Cleanup following the render stage implementation and remove the completed roadmap
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
Narratio is a stage-driven Go orchestrator for turning D&D session audio into polished transcripts and generated artifacts.
|
||||
|
||||
It runs a deterministic workflow across `prepare`, `transcribe`, `merge`, `polish`, `normalize`, `trim`, `analyze`, and `publish`, with manifest-driven continuation and restore support.
|
||||
It runs a deterministic workflow across `prepare`, `transcribe`, `merge`, `polish`, `normalize`, `trim`, `render`, `analyze`, and `publish`, with manifest-driven continuation and restore support.
|
||||
|
||||
```bash
|
||||
narratio run 2026-04-04
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
# Roadmap: Render Stage
|
||||
|
||||
Status: Completed
|
||||
|
||||
This roadmap defines a post-1.0 feature addition: a new `render` stage that uses `seriatim render` to produce human-readable Markdown versions of the final transcript artifacts.
|
||||
|
||||
Planned behavior belongs only in this roadmap until implementation lands. Current-behavior docs, examples, and command references must be updated only after the code is implemented and tested.
|
||||
|
||||
## Goal
|
||||
|
||||
Add a first-class stage between `trim` and `analyze`:
|
||||
|
||||
1. `prepare`
|
||||
2. `transcribe`
|
||||
3. `merge`
|
||||
4. `polish`
|
||||
5. `normalize`
|
||||
6. `trim`
|
||||
7. `render`
|
||||
8. `analyze`
|
||||
9. `publish`
|
||||
10. `notify`
|
||||
|
||||
The stage renders Markdown versions of:
|
||||
|
||||
- `narratio.transcript.final`
|
||||
- `narratio.transcript.final_trimmed`
|
||||
|
||||
The stage produces new built-in artifacts:
|
||||
|
||||
| Source ID | Canonical path | Output kind |
|
||||
| --- | --- | --- |
|
||||
| `narratio.transcript.final_markdown` | `transcripts/final.md` | `transcript_final_markdown` |
|
||||
| `narratio.transcript.final_trimmed_markdown` | `transcripts/final.trimmed.md` | `transcript_final_trimmed_markdown` |
|
||||
|
||||
Default publish outputs should include:
|
||||
|
||||
- `narratio.transcript.final_trimmed`
|
||||
- `narratio.transcript.final_markdown`
|
||||
- `narratio.transcript.final_trimmed_markdown`
|
||||
|
||||
## Public Contract
|
||||
|
||||
Add `pipeline.render` with strict YAML decoding.
|
||||
|
||||
Fields:
|
||||
|
||||
| Field | Type | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `enabled` | bool | `true` | optional |
|
||||
| `format` | string | `markdown` | only `markdown` is supported |
|
||||
| `title` | string | empty | optional |
|
||||
| `include_timestamps` | bool | `true` | optional |
|
||||
| `include_segment_ids` | bool | `false` | optional |
|
||||
| `include_metadata` | bool | `false` | optional |
|
||||
|
||||
Title behavior:
|
||||
|
||||
- if `pipeline.render.title` is non-empty, pass it as `--title`;
|
||||
- otherwise, if `session.title` is non-empty, pass `session.title` as `--title`;
|
||||
- otherwise, omit `--title` and let Seriatim use its default.
|
||||
|
||||
The initial implementation supports only Markdown. Future formats require explicit config validation and artifact naming decisions.
|
||||
|
||||
## Implementation Stages
|
||||
|
||||
### Stage 1: Artifact, Config, and Adapter Contracts
|
||||
|
||||
- Extend the transcript artifact model with the two Markdown built-ins.
|
||||
- Register Markdown artifacts as text content in the artifact registry so they work in artifact resolution, publish outputs, locks, status, artifacts list, and Scriptorium inputs.
|
||||
- Add `RenderConfig` under `PipelineConfig` and apply defaults in the config loader/defaulting path.
|
||||
- Validate `pipeline.render.format` as `markdown` and keep unknown fields rejected by strict YAML decoding.
|
||||
- Extend the Seriatim adapter interface with `Render(ctx, RenderRequest)`.
|
||||
- Add subprocess support for `seriatim render` with:
|
||||
- `--input-file`
|
||||
- `--output-file`
|
||||
- `--format markdown`
|
||||
- optional `--title`
|
||||
- explicit boolean behavior for timestamps, segment IDs, and metadata.
|
||||
- Validate render output as non-empty text, not JSON.
|
||||
- Write Seriatim render stdout/stderr logs and generated invocation config consistently with existing Seriatim stage calls.
|
||||
|
||||
### Stage 2: Render Stage Runtime
|
||||
|
||||
- Add `renderStage` to the stage package.
|
||||
- Insert `renderStage{}` into `stage.All()` after `trimStage{}` and before `analyzeStage{}`.
|
||||
- Make `run-stage render <session_id>` work through the existing stage selection path.
|
||||
- If render is disabled, mark the stage succeeded with metadata and no outputs.
|
||||
- If enabled, resolve inputs manifest-first using existing artifact resolution:
|
||||
- final transcript from `narratio.transcript.final`;
|
||||
- final trimmed transcript from `narratio.transcript.final_trimmed`.
|
||||
- Render run-local Markdown outputs first, then materialize canonical outputs:
|
||||
- `transcripts/final.md`
|
||||
- `transcripts/final.trimmed.md`
|
||||
- Record manifest outputs with the new output kinds and source IDs.
|
||||
- Record metadata for input paths/provenance, canonical/run-local output paths, format, resolved title, boolean render settings, adapter duration/exit code/binary, and adapter metadata.
|
||||
- On missing required JSON inputs, fail clearly with guidance to run `normalize` or `trim` as appropriate.
|
||||
|
||||
### Stage 3: Publish, Analyze, Docs, and Examples
|
||||
|
||||
- Update default publish outputs to include both Markdown artifacts in addition to final trimmed JSON.
|
||||
- Ensure publish output destination derivation works for Markdown built-ins through the shared artifact policy path.
|
||||
- Ensure Scriptorium input validation accepts Markdown built-ins as ordinary built-in sources.
|
||||
- Update analyze missing-input guidance so required Markdown built-in inputs point operators to `run-stage render`.
|
||||
- Update user/operator/internal docs only after implementation:
|
||||
- `docs/cli.md`
|
||||
- `docs/config.md`
|
||||
- `docs/operations.md`
|
||||
- `docs/internal/README.md`
|
||||
- `docs/internal/artifacts.md`
|
||||
- new `docs/internal/stage-render.md`
|
||||
- `docs/integrations/seriatim.md`
|
||||
- Update examples only where useful; defaults should work without an explicit `pipeline.render` block.
|
||||
|
||||
## Seriatim Adapter Contract
|
||||
|
||||
Add a narrow render request/result beside the existing merge, normalize, and trim contracts.
|
||||
|
||||
Request fields:
|
||||
|
||||
- binary
|
||||
- input transcript path
|
||||
- output Markdown path
|
||||
- format
|
||||
- title
|
||||
- include timestamps
|
||||
- include segment IDs
|
||||
- include metadata
|
||||
- stdout log path
|
||||
- stderr log path
|
||||
- generated config path
|
||||
- timeout
|
||||
|
||||
Result fields:
|
||||
|
||||
- output path
|
||||
- stdout log path
|
||||
- stderr log path
|
||||
- generated config path
|
||||
- exit code
|
||||
- duration
|
||||
- invoked binary
|
||||
- format
|
||||
- title
|
||||
- metadata
|
||||
|
||||
The adapter owns subprocess command construction and validation of the non-empty output file. Stage logic should express intent in Narratio terms and should not construct subprocess arguments directly.
|
||||
|
||||
## Testing Guidance
|
||||
|
||||
Focused tests:
|
||||
|
||||
- `internal/artifactmodel` and `internal/artifacts`
|
||||
- new Markdown source IDs, canonical paths, output kinds, producer stage, text validation, catalog ordering;
|
||||
- resolver fallback from canonical Markdown paths;
|
||||
- publish destination derivation for Markdown built-ins.
|
||||
- `internal/config`
|
||||
- render defaults;
|
||||
- strict decode rejects unknown render fields;
|
||||
- invalid format fails validation;
|
||||
- default publish outputs include final trimmed JSON plus both Markdown artifacts.
|
||||
- `internal/adapters/seriatim`
|
||||
- render command args;
|
||||
- title omission vs explicit title;
|
||||
- boolean flag behavior;
|
||||
- generated config;
|
||||
- stdout/stderr logs;
|
||||
- non-empty output validation;
|
||||
- failure wrapping.
|
||||
- `internal/stage`
|
||||
- render resolves final and final-trimmed inputs from manifest outputs before canonical fallback;
|
||||
- render writes run-local outputs and materializes canonical Markdown outputs;
|
||||
- render records manifest outputs, logs, generated configs, and metadata;
|
||||
- disabled render succeeds without outputs;
|
||||
- missing final/final-trimmed inputs fail clearly.
|
||||
- `internal/app`
|
||||
- full plan order includes `render`;
|
||||
- `run-stage render <session_id>` works;
|
||||
- force rerunning render marks analyze, publish, and notify stale;
|
||||
- status and artifacts list include Markdown built-ins;
|
||||
- publish defaults include Markdown outputs.
|
||||
|
||||
Validation commands:
|
||||
|
||||
- `go test ./internal/artifactmodel ./internal/artifacts -v`
|
||||
- `go test ./internal/config -v`
|
||||
- `go test ./internal/adapters/seriatim -v`
|
||||
- `go test ./internal/stage -run Render -v`
|
||||
- `go test ./internal/app -run 'Plan|RunStage|Publish|Artifacts|Status' -v`
|
||||
- `go test ./...`
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Do not change existing JSON transcript source IDs, canonical paths, or output kinds.
|
||||
- Do not make Markdown output paths configurable in the first implementation.
|
||||
- Do not add additional render formats before the format naming and artifact naming contract is defined.
|
||||
- Do not move Seriatim subprocess details into stage logic.
|
||||
- Do not document the render stage as implemented outside this roadmap until implementation lands.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- `render` is enabled by default.
|
||||
- Markdown canonical paths are fixed built-in artifact paths.
|
||||
- `format: markdown` is the only supported initial format.
|
||||
- Both Markdown outputs are included in default publish outputs.
|
||||
- Existing publish layout and current-state commit behavior remain unchanged.
|
||||
@@ -672,7 +672,7 @@ func resolveScriptoriumInput(
|
||||
return "", false, nil, fmt.Errorf("trimmed transcript input is unavailable; run trim stage first")
|
||||
case artifacts.ArtifactTranscriptFinalMarkdown, artifacts.ArtifactTranscriptFinalTrimmedMarkdown:
|
||||
return "", false, nil, fmt.Errorf(
|
||||
"rendered markdown transcript input is unavailable for source %q; run narratio run-stage --force render %s",
|
||||
"rendered markdown transcript input is unavailable for source %q; run narratio run-stage render %s --force",
|
||||
descriptor.Source.ID,
|
||||
paths.SessionID,
|
||||
)
|
||||
|
||||
@@ -1083,7 +1083,7 @@ func TestAnalyzeFailsWhenRenderedMarkdownTranscriptMissing(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage --force render") {
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage render") || !strings.Contains(err.Error(), "--force") {
|
||||
t.Fatalf("error = %q, want render guidance", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -1101,7 +1101,7 @@ func TestAnalyzeFailsWhenRenderedTrimmedMarkdownTranscriptMissing(t *testing.T)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage --force render") {
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage render") || !strings.Contains(err.Error(), "--force") {
|
||||
t.Fatalf("error = %q, want render guidance", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (normalizeStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (
|
||||
stderrPath = filepath.Join(runLayout.LogsDir, "seriatim.normalize.stderr.log")
|
||||
generatedConfigPath = filepath.Join(runLayout.ConfigDir, "seriatim.normalize.generated.yml")
|
||||
}
|
||||
timeout, err := resolveTrimSeriatimTimeout(env.Config.Pipeline.Seriatim.Timeout)
|
||||
timeout, err := resolveSeriatimStageTimeout(env.Config.Pipeline.Seriatim.Timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("normalize: resolve seriatim timeout: %w", err)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ func (renderStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
|
||||
finalTrimmedGeneratedConfigPath = filepath.Join(runLayout.ConfigDir, "seriatim.render.final_trimmed.generated.yml")
|
||||
}
|
||||
|
||||
timeout, err := resolveTrimSeriatimTimeout(env.Config.Pipeline.Seriatim.Timeout)
|
||||
timeout, err := resolveSeriatimStageTimeout(env.Config.Pipeline.Seriatim.Timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render: resolve seriatim timeout: %w", err)
|
||||
}
|
||||
@@ -260,7 +260,7 @@ func wrapRenderInputResolveError(err error, sessionID, sourceID, guidanceStage s
|
||||
var notFound *artifacts.SessionArtifactNotFoundError
|
||||
if errors.As(err, ¬Found) {
|
||||
return fmt.Errorf(
|
||||
"render: required input %q is unavailable; run narratio run-stage --force %s %s",
|
||||
"render: required input %q is unavailable; run narratio run-stage %s %s --force",
|
||||
sourceID,
|
||||
guidanceStage,
|
||||
sessionID,
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestRenderStageFailsWhenFinalInputMissing(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage --force normalize") {
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage normalize") || !strings.Contains(err.Error(), "--force") {
|
||||
t.Fatalf("error = %q, want normalize guidance", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func TestRenderStageFailsWhenFinalTrimmedInputMissing(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage --force trim") {
|
||||
if !strings.Contains(err.Error(), "run narratio run-stage trim") || !strings.Contains(err.Error(), "--force") {
|
||||
t.Fatalf("error = %q, want trim guidance", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ func (trimStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*Stag
|
||||
trimStderrLogPath = filepath.Join(runLayout.LogsDir, "seriatim.trim.stderr.log")
|
||||
trimGeneratedConfigPath = filepath.Join(runLayout.ConfigDir, "seriatim.trim.generated.yml")
|
||||
}
|
||||
trimTimeout, err := resolveTrimSeriatimTimeout(env.Config.Pipeline.Seriatim.Timeout)
|
||||
trimTimeout, err := resolveSeriatimStageTimeout(env.Config.Pipeline.Seriatim.Timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("trim: resolve seriatim timeout: %w", err)
|
||||
}
|
||||
@@ -398,7 +398,7 @@ func copyTranscript(store artifacts.Store, src, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveTrimSeriatimTimeout(raw string) (time.Duration, error) {
|
||||
func resolveSeriatimStageTimeout(raw string) (time.Duration, error) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return 0, nil
|
||||
|
||||
Reference in New Issue
Block a user