Cleaned up and removed legacy configuration surfaces
This commit is contained in:
@@ -267,8 +267,6 @@ Operational notes:
|
|||||||
| `pipeline.workspace.cleanup_after_archive` | bool | No | `false` |
|
| `pipeline.workspace.cleanup_after_archive` | bool | No | `false` |
|
||||||
| `pipeline.secrets.env_dir` | string | Conditional | none |
|
| `pipeline.secrets.env_dir` | string | Conditional | none |
|
||||||
| `pipeline.storage.backend` | string | No | empty |
|
| `pipeline.storage.backend` | string | No | empty |
|
||||||
| `pipeline.storage.bucket` | string | No | empty |
|
|
||||||
| `pipeline.storage.prefix` | string | No | empty |
|
|
||||||
| `pipeline.storage.s3.bucket` | string | Conditional | empty |
|
| `pipeline.storage.s3.bucket` | string | Conditional | empty |
|
||||||
| `pipeline.storage.s3.root_prefix` | string | No | `dnd` |
|
| `pipeline.storage.s3.root_prefix` | string | No | `dnd` |
|
||||||
| `pipeline.storage.s3.region` | string | No | empty |
|
| `pipeline.storage.s3.region` | string | No | empty |
|
||||||
@@ -349,10 +347,6 @@ Operational notes:
|
|||||||
| `pipeline.scriptorium.artifacts.<name>.inputs.<key>.path` | string | No | empty |
|
| `pipeline.scriptorium.artifacts.<name>.inputs.<key>.path` | string | No | empty |
|
||||||
| `pipeline.scriptorium.artifacts.<name>.inputs.<key>.required` | bool | No | `false` |
|
| `pipeline.scriptorium.artifacts.<name>.inputs.<key>.required` | bool | No | `false` |
|
||||||
| `pipeline.scriptorium.artifacts.<name>.vars.<key>` | map value | No | empty |
|
| `pipeline.scriptorium.artifacts.<name>.vars.<key>` | map value | No | empty |
|
||||||
| `pipeline.analyzer.binary_path` | string | No | empty |
|
|
||||||
| `pipeline.analyzer.timeout` | duration string | No | empty |
|
|
||||||
| `pipeline.analyzer.artifacts.output_dir` | string | No | empty |
|
|
||||||
| `pipeline.analyzer.artifacts.types[]` | list[string] | No | empty |
|
|
||||||
| `pipeline.notification.backend` | string | No | empty |
|
| `pipeline.notification.backend` | string | No | empty |
|
||||||
| `pipeline.notification.recipient` | string | No | empty |
|
| `pipeline.notification.recipient` | string | No | empty |
|
||||||
| `pipeline.notification.timeout` | duration string | No | empty |
|
| `pipeline.notification.timeout` | duration string | No | empty |
|
||||||
@@ -378,7 +372,6 @@ Allowed `pipeline.scriptorium.artifacts.<name>.inputs.<key>.source` values:
|
|||||||
- `narratio.transcript.trimmed`
|
- `narratio.transcript.trimmed`
|
||||||
- `narratio.bounds.session`
|
- `narratio.bounds.session`
|
||||||
- `narratio.artifact.<configured_artifact_key>`
|
- `narratio.artifact.<configured_artifact_key>`
|
||||||
- `previous_session_artifact` (legacy path-based source; uses `inputs.<key>.path`)
|
|
||||||
|
|
||||||
`pipeline.archive.promote_artifacts[].source` values:
|
`pipeline.archive.promote_artifacts[].source` values:
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ Runtime env boundary fields (`internal/stage.Env`):
|
|||||||
- `scriptorium.Runner`
|
- `scriptorium.Runner`
|
||||||
- `storage.ObjectStore`
|
- `storage.ObjectStore`
|
||||||
- `notify.Sender`
|
- `notify.Sender`
|
||||||
- `analyzer.Runner`
|
|
||||||
|
|
||||||
Current execution usage:
|
Current execution usage:
|
||||||
- Actively used by implemented stages: `WhisperX`, `Seriatim`, `Audita`, `Scriptorium`, `ObjectStore`, `Notifier`.
|
- Actively used by implemented stages: `WhisperX`, `Seriatim`, `Audita`, `Scriptorium`, `ObjectStore`, `Notifier`.
|
||||||
- Present but not used by implemented stage set: `Analyzer`, legacy `storage.Backend`.
|
- Present but not used by implemented stage set: legacy `storage.Backend`.
|
||||||
|
|
||||||
Default construction in app runner:
|
Default construction in app runner:
|
||||||
- Auto-constructed when not injected: WhisperX HTTP client, Seriatim subprocess runner, Audita subprocess runner, Scriptorium subprocess runner, object store (only when needed), and `notify.NoopSender`.
|
- Auto-constructed when not injected: WhisperX HTTP client, Seriatim subprocess runner, Audita subprocess runner, Scriptorium subprocess runner, object store (only when needed), and `notify.NoopSender`.
|
||||||
@@ -71,7 +70,6 @@ Default construction in app runner:
|
|||||||
- `internal/adapters/scriptorium/subprocess_test.go`
|
- `internal/adapters/scriptorium/subprocess_test.go`
|
||||||
- `internal/adapters/storage/*_test.go`
|
- `internal/adapters/storage/*_test.go`
|
||||||
- `internal/adapters/notify/fake_test.go`
|
- `internal/adapters/notify/fake_test.go`
|
||||||
- `internal/adapters/analyzer/fake_test.go`
|
|
||||||
- `internal/app/runner_test.go`
|
- `internal/app/runner_test.go`
|
||||||
|
|
||||||
## Architectural invariants
|
## Architectural invariants
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ Inputs:
|
|||||||
Source types used by analyze:
|
Source types used by analyze:
|
||||||
- built-ins: `narratio.transcript.*`, `narratio.bounds.session`;
|
- built-ins: `narratio.transcript.*`, `narratio.bounds.session`;
|
||||||
- configured artifacts: `narratio.artifact.<artifact_key>`;
|
- configured artifacts: `narratio.artifact.<artifact_key>`;
|
||||||
- canonical previous-session artifacts: `narratio.previous_session.artifact.<artifact_key>`;
|
- canonical previous-session artifacts: `narratio.previous_session.artifact.<artifact_key>`.
|
||||||
- legacy path-based previous-session source: `previous_session_artifact` (uses `inputs.*.path`).
|
|
||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
- promoted configured artifact files at each configured `output_path`;
|
- promoted configured artifact files at each configured `output_path`;
|
||||||
|
|||||||
159
docs/roadmap/cleanup.md
Normal file
159
docs/roadmap/cleanup.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# Roadmap: Legacy Config Cleanup
|
||||||
|
|
||||||
|
Status: Implemented
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Narratio's current pipeline config schema still accepts fields that predate the current storage, artifact, and previous-session models:
|
||||||
|
|
||||||
|
- `pipeline.storage.bucket`
|
||||||
|
- `pipeline.storage.prefix`
|
||||||
|
- `pipeline.analyzer.*`
|
||||||
|
- `previous_session_artifact`
|
||||||
|
|
||||||
|
These names make the config reference harder to trust because they suggest supported behavior that operators should no longer use. The modern interface is:
|
||||||
|
|
||||||
|
- `pipeline.storage.s3.*` for remote storage.
|
||||||
|
- Scriptorium configured artifacts under `pipeline.scriptorium.artifacts`.
|
||||||
|
- Canonical artifact source IDs such as `narratio.artifact.<configured_artifact_key>`.
|
||||||
|
- Canonical previous-session artifact sources such as `narratio.previous_session.artifact.<configured_artifact_key>`.
|
||||||
|
|
||||||
|
Strict YAML decoding should reject removed legacy fields once this cleanup lands.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
`pipeline.storage.bucket` and `pipeline.storage.prefix` were inert compatibility fields and have been removed:
|
||||||
|
|
||||||
|
- They are no longer present on `config.StorageConfig`.
|
||||||
|
- Strict decoding rejects them.
|
||||||
|
- Runtime S3 behavior uses `pipeline.storage.s3.bucket` and `pipeline.storage.s3.root_prefix`.
|
||||||
|
- No current code reads the top-level storage bucket or prefix fields.
|
||||||
|
|
||||||
|
`pipeline.analyzer.*` was legacy code surface and has been removed:
|
||||||
|
|
||||||
|
- `config.PipelineConfig` no longer includes analyzer config.
|
||||||
|
- Strict decoding rejects `pipeline.analyzer`.
|
||||||
|
- `stage.Env` no longer exposes an analyzer runner, and `internal/adapters/analyzer` has been deleted.
|
||||||
|
- Modern analyze execution is Scriptorium-backed; the analyzer adapter is not used by current stage execution.
|
||||||
|
|
||||||
|
`previous_session_artifact` was a live legacy behavior and has been removed:
|
||||||
|
|
||||||
|
- Config validation rejects it as an unsupported Scriptorium input source.
|
||||||
|
- The analyze stage no longer has path-based previous-artifact resolution through `inputs.<name>.path`.
|
||||||
|
- Tests cover canonical previous-session sources and the rejection of the legacy source.
|
||||||
|
- The canonical replacement is `narratio.previous_session.artifact.<configured_artifact_key>`, resolved through the previous-session cache/catalog model.
|
||||||
|
|
||||||
|
## Target Model
|
||||||
|
|
||||||
|
The pipeline config schema should expose only current behavior:
|
||||||
|
|
||||||
|
- Remote storage is configured only through `pipeline.storage.s3.*`.
|
||||||
|
- Generated artifacts are configured only through `pipeline.scriptorium.artifacts`.
|
||||||
|
- Scriptorium artifact inputs use canonical source IDs.
|
||||||
|
- Previous-session artifact inputs use `narratio.previous_session.artifact.<configured_artifact_key>`.
|
||||||
|
- Unknown legacy fields fail strict YAML decoding.
|
||||||
|
|
||||||
|
No compatibility aliases should remain unless a future migration requirement explicitly reintroduces them.
|
||||||
|
|
||||||
|
## Cleanup Order
|
||||||
|
|
||||||
|
### Stage 1: Remove Inert Storage Compatibility Fields
|
||||||
|
|
||||||
|
Status: Implemented
|
||||||
|
|
||||||
|
Remove `pipeline.storage.bucket` and `pipeline.storage.prefix`.
|
||||||
|
|
||||||
|
Implementation requirements:
|
||||||
|
|
||||||
|
- Delete `StorageConfig.Bucket` and `StorageConfig.Prefix`.
|
||||||
|
- Keep `StorageConfig.Backend` and `StorageConfig.S3`.
|
||||||
|
- Confirm all runtime storage paths continue to use `storage.s3.bucket` and `storage.s3.root_prefix`.
|
||||||
|
- Update examples and docs to remove top-level storage `bucket` and `prefix`.
|
||||||
|
- Add or update strict-decode tests proving `pipeline.storage.bucket` and `pipeline.storage.prefix` are rejected.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Existing S3 workflows still pass with `pipeline.storage.s3.bucket`.
|
||||||
|
- Pipeline configs containing top-level `storage.bucket` or `storage.prefix` fail to load.
|
||||||
|
- No docs or examples present those fields as available.
|
||||||
|
|
||||||
|
### Stage 2: Remove Legacy Analyzer Schema and Adapter Surface
|
||||||
|
|
||||||
|
Status: Implemented
|
||||||
|
|
||||||
|
Remove the unused analyzer configuration and adapter contract.
|
||||||
|
|
||||||
|
Implementation requirements:
|
||||||
|
|
||||||
|
- Delete `PipelineConfig.Analyzer`.
|
||||||
|
- Delete `AnalyzerConfig` and `ArtifactSettings`.
|
||||||
|
- Remove analyzer timeout validation.
|
||||||
|
- Remove `stage.Env.Analyzer`.
|
||||||
|
- Delete `internal/adapters/analyzer` if no remaining code imports it.
|
||||||
|
- Remove `pipeline.analyzer.*` from tests, examples, and docs.
|
||||||
|
- Add or update strict-decode tests proving `pipeline.analyzer` is rejected.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Analyze behavior remains fully Scriptorium-backed.
|
||||||
|
- No runtime code imports `internal/adapters/analyzer`.
|
||||||
|
- Pipeline configs containing `pipeline.analyzer` fail to load.
|
||||||
|
- Contributor and internal adapter docs no longer list the analyzer adapter.
|
||||||
|
|
||||||
|
### Stage 3: Remove Path-Based Previous Session Artifact Source
|
||||||
|
|
||||||
|
Status: Implemented
|
||||||
|
|
||||||
|
Remove `previous_session_artifact` and require canonical previous-session artifact sources.
|
||||||
|
|
||||||
|
Implementation requirements:
|
||||||
|
|
||||||
|
- Remove `previous_session_artifact` from supported Scriptorium input sources.
|
||||||
|
- Remove analyze-stage special-case handling that resolves `inputs.<name>.path` for previous artifacts.
|
||||||
|
- Keep canonical handling for `narratio.previous_session.artifact.<configured_artifact_key>`.
|
||||||
|
- Rewrite tests that use `previous_session_artifact` to use canonical sources and prepared previous-cache fixtures.
|
||||||
|
- Add validation tests proving `previous_session_artifact` is rejected.
|
||||||
|
- Update docs to remove the legacy path-based source and document only canonical previous-session sources.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- `pipeline.scriptorium.artifacts.*.inputs.*.source: previous_session_artifact` fails validation.
|
||||||
|
- Canonical previous-session sources continue to work for required and optional inputs.
|
||||||
|
- Prepare/restore previous-cache behavior remains unchanged.
|
||||||
|
- No docs or examples mention `previous_session_artifact` as supported.
|
||||||
|
|
||||||
|
## Test Guidance
|
||||||
|
|
||||||
|
Run focused tests after each stage:
|
||||||
|
|
||||||
|
- `go test ./internal/config -v`
|
||||||
|
- `go test ./internal/stage -run Analyze -v`
|
||||||
|
- `go test ./internal/app -v`
|
||||||
|
- `go test ./...`
|
||||||
|
|
||||||
|
For Stage 1, focus on config load/strict-decode and S3 workflow regression tests.
|
||||||
|
|
||||||
|
For Stage 2, focus on compile-time removal, config strict-decode tests, and full app/stage tests to catch stale adapter references.
|
||||||
|
|
||||||
|
For Stage 3, focus on Scriptorium config validation, analyze-stage input resolution, previous-cache behavior, and restore/analyze workflows.
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
|
||||||
|
Update current-behavior docs only after the corresponding code removal lands:
|
||||||
|
|
||||||
|
- `docs/config.md`
|
||||||
|
- `docs/cli.md`, only if command behavior text references removed fields.
|
||||||
|
- `docs/operations.md`, only if operator workflow text references removed fields.
|
||||||
|
- `docs/internal/stage-analyze.md`
|
||||||
|
- `docs/internal/adapters.md`
|
||||||
|
- `examples/pipeline.full.annotated.yml`
|
||||||
|
- `examples/pipeline.production.yml`
|
||||||
|
|
||||||
|
Do not preserve removed fields in examples as compatibility notes. The goal is to make strict config behavior and documentation line up.
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
- This is a hard cleanup; no backward-compatible aliases are retained.
|
||||||
|
- Current production configs can be migrated to `storage.s3.*`, Scriptorium artifacts, and canonical previous-session sources before this lands.
|
||||||
|
- Removing the unused analyzer adapter does not block any active stage behavior.
|
||||||
|
- The cleanup should be implemented in the listed order so inert schema removal is separated from behavior removal.
|
||||||
@@ -14,9 +14,6 @@ workspace:
|
|||||||
storage:
|
storage:
|
||||||
# Optional storage backend selector; use "s3" for archive + S3 audio workflows.
|
# Optional storage backend selector; use "s3" for archive + S3 audio workflows.
|
||||||
backend: s3
|
backend: s3
|
||||||
# Compatibility fields retained in schema.
|
|
||||||
bucket: ""
|
|
||||||
prefix: ""
|
|
||||||
s3:
|
s3:
|
||||||
# Required when using S3 audio or S3 archive uploads.
|
# Required when using S3 audio or S3 archive uploads.
|
||||||
bucket: my-dnd-archive
|
bucket: my-dnd-archive
|
||||||
@@ -165,14 +162,6 @@ scriptorium:
|
|||||||
campaign_name: true
|
campaign_name: true
|
||||||
output_kind: player_handout
|
output_kind: player_handout
|
||||||
|
|
||||||
analyzer:
|
|
||||||
# Optional adapter settings.
|
|
||||||
binary_path: ""
|
|
||||||
timeout: 2m
|
|
||||||
artifacts:
|
|
||||||
output_dir: ""
|
|
||||||
types: []
|
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
# Optional notification settings.
|
# Optional notification settings.
|
||||||
backend: ""
|
backend: ""
|
||||||
|
|||||||
@@ -108,8 +108,5 @@ scriptorium:
|
|||||||
session_id: true
|
session_id: true
|
||||||
output_kind: player_handout
|
output_kind: player_handout
|
||||||
|
|
||||||
analyzer:
|
|
||||||
timeout: 2m
|
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 30s
|
timeout: 30s
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package analyzer
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
// NoopRunner is a deterministic no-op analyzer adapter.
|
|
||||||
type NoopRunner struct{}
|
|
||||||
|
|
||||||
// Run returns the requested output path with placeholder metadata.
|
|
||||||
func (n *NoopRunner) Run(ctx context.Context, req AnalyzeRequest) (AnalyzeResult, error) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return AnalyzeResult{}, err
|
|
||||||
}
|
|
||||||
return AnalyzeResult{ArtifactPath: req.OutputPath, Metadata: map[string]any{"placeholder": true}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FakeRunner captures analyze requests and returns deterministic responses.
|
|
||||||
type FakeRunner struct {
|
|
||||||
Requests []AnalyzeRequest
|
|
||||||
Err error
|
|
||||||
Result AnalyzeResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run records request and returns configured response.
|
|
||||||
func (f *FakeRunner) Run(ctx context.Context, req AnalyzeRequest) (AnalyzeResult, error) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return AnalyzeResult{}, err
|
|
||||||
}
|
|
||||||
f.Requests = append(f.Requests, req)
|
|
||||||
if f.Err != nil {
|
|
||||||
return AnalyzeResult{}, f.Err
|
|
||||||
}
|
|
||||||
res := f.Result
|
|
||||||
if res.ArtifactPath == "" {
|
|
||||||
res.ArtifactPath = req.OutputPath
|
|
||||||
}
|
|
||||||
if res.Metadata == nil {
|
|
||||||
res.Metadata = map[string]any{"fake": true}
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package analyzer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFakeRunnerCapturesRequestAndReturnsPath(t *testing.T) {
|
|
||||||
fake := &FakeRunner{}
|
|
||||||
req := AnalyzeRequest{ArtifactType: "session-log", OutputPath: "artifacts/session-log.md"}
|
|
||||||
|
|
||||||
res, err := fake.Run(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Run() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(fake.Requests) != 1 || fake.Requests[0].ArtifactType != "session-log" {
|
|
||||||
t.Fatalf("requests = %#v, want captured request", fake.Requests)
|
|
||||||
}
|
|
||||||
if res.ArtifactPath != req.OutputPath {
|
|
||||||
t.Fatalf("artifact path = %q, want %q", res.ArtifactPath, req.OutputPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFakeRunnerError(t *testing.T) {
|
|
||||||
fake := &FakeRunner{Err: errors.New("boom")}
|
|
||||||
_, err := fake.Run(context.Background(), AnalyzeRequest{})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Package analyzer declares the adapter contract for artifact analysis generation.
|
|
||||||
package analyzer
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
// TODO: implement analyzer integration once the analyzer contract is finalized.
|
|
||||||
|
|
||||||
// Runner is the adapter boundary for analyzer invocations.
|
|
||||||
type Runner interface {
|
|
||||||
Run(ctx context.Context, req AnalyzeRequest) (AnalyzeResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AnalyzeRequest describes one analyzer artifact generation request.
|
|
||||||
type AnalyzeRequest struct {
|
|
||||||
ArtifactType string
|
|
||||||
ProcessedTranscriptPath string
|
|
||||||
ContextReferences []string
|
|
||||||
OutputPath string
|
|
||||||
GeneratedConfigPath string
|
|
||||||
StdoutLogPath string
|
|
||||||
StderrLogPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AnalyzeResult describes analyzer output.
|
|
||||||
type AnalyzeResult struct {
|
|
||||||
ArtifactPath string
|
|
||||||
Metadata map[string]any
|
|
||||||
}
|
|
||||||
@@ -203,8 +203,6 @@ seriatim:
|
|||||||
audita:
|
audita:
|
||||||
binary: ` + auditaBinary + `
|
binary: ` + auditaBinary + `
|
||||||
llm_api_key_env: OPENROUTER_API_KEY
|
llm_api_key_env: OPENROUTER_API_KEY
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
`
|
`
|
||||||
@@ -268,8 +266,6 @@ seriatim:
|
|||||||
binary: seriatim
|
binary: seriatim
|
||||||
audita:
|
audita:
|
||||||
binary: audita
|
binary: audita
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
`
|
`
|
||||||
@@ -435,10 +431,6 @@ seriatim:
|
|||||||
report: true
|
report: true
|
||||||
audita:
|
audita:
|
||||||
binary: ` + auditaBinary + `
|
binary: ` + auditaBinary + `
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
artifacts:
|
|
||||||
output_dir: artifacts
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -108,8 +108,6 @@ seriatim:
|
|||||||
binary: seriatim
|
binary: seriatim
|
||||||
audita:
|
audita:
|
||||||
binary: audita
|
binary: audita
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1067,8 +1067,6 @@ func TestBuildDefaultRunnersWithOmittedToolSections(t *testing.T) {
|
|||||||
root: ` + t.TempDir() + `
|
root: ` + t.TempDir() + `
|
||||||
whisperx:
|
whisperx:
|
||||||
transcribe_url: https://example.com/transcribe
|
transcribe_url: https://example.com/transcribe
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ func ResolvePreviousSessionArtifactWithCatalog(
|
|||||||
ID: source,
|
ID: source,
|
||||||
Path: candidate,
|
Path: candidate,
|
||||||
ProducerStage: "prepare",
|
ProducerStage: "prepare",
|
||||||
OutputKind: "previous_session_artifact",
|
OutputKind: "previous_session_cache",
|
||||||
Provenance: ArtifactProvenancePreviousCacheManifestInput,
|
Provenance: ArtifactProvenancePreviousCacheManifestInput,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -284,7 +284,7 @@ func ResolvePreviousSessionArtifactWithCatalog(
|
|||||||
ID: source,
|
ID: source,
|
||||||
Path: fallback,
|
Path: fallback,
|
||||||
ProducerStage: "prepare",
|
ProducerStage: "prepare",
|
||||||
OutputKind: "previous_session_artifact",
|
OutputKind: "previous_session_cache",
|
||||||
Provenance: ArtifactProvenancePreviousCacheFilesystem,
|
Provenance: ArtifactProvenancePreviousCacheFilesystem,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ func TestCacheDefaults(t *testing.T) {
|
|||||||
root: /tmp/narratio
|
root: /tmp/narratio
|
||||||
whisperx:
|
whisperx:
|
||||||
transcribe_url: https://example.com/transcribe
|
transcribe_url: https://example.com/transcribe
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
`, `session_id: 2026-05-03
|
`, `session_id: 2026-05-03
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ func writeCampaignConfigTestFiles(t *testing.T, campaignYAML, sessionYAML string
|
|||||||
campaignPath := filepath.Join(dir, "campaign.yml")
|
campaignPath := filepath.Join(dir, "campaign.yml")
|
||||||
sessionPath := filepath.Join(dir, "session.yml")
|
sessionPath := filepath.Join(dir, "session.yml")
|
||||||
|
|
||||||
pipelineYAML := "workspace:\n root: " + filepath.ToSlash(filepath.Join(dir, "work")) + "\nwhisperx:\n transcribe_url: https://example.com/transcribe\nanalyzer:\n timeout: 20m\nnotification:\n timeout: 10s\n"
|
pipelineYAML := "workspace:\n root: " + filepath.ToSlash(filepath.Join(dir, "work")) + "\nwhisperx:\n transcribe_url: https://example.com/transcribe\nnotification:\n timeout: 10s\n"
|
||||||
if err := os.WriteFile(pipelinePath, []byte(pipelineYAML), 0o644); err != nil {
|
if err := os.WriteFile(pipelinePath, []byte(pipelineYAML), 0o644); err != nil {
|
||||||
t.Fatalf("write pipeline.yml: %v", err)
|
t.Fatalf("write pipeline.yml: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ type PipelineConfig struct {
|
|||||||
Normalize *NormalizeConfig `yaml:"normalize"`
|
Normalize *NormalizeConfig `yaml:"normalize"`
|
||||||
Trim *TrimConfig `yaml:"trim"`
|
Trim *TrimConfig `yaml:"trim"`
|
||||||
Scriptorium *ScriptoriumConfig `yaml:"scriptorium"`
|
Scriptorium *ScriptoriumConfig `yaml:"scriptorium"`
|
||||||
Analyzer AnalyzerConfig `yaml:"analyzer"`
|
|
||||||
Notification NotificationConfig `yaml:"notification"`
|
Notification NotificationConfig `yaml:"notification"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +69,6 @@ type SecretsConfig struct {
|
|||||||
// StorageConfig configures storage backends and related parameters.
|
// StorageConfig configures storage backends and related parameters.
|
||||||
type StorageConfig struct {
|
type StorageConfig struct {
|
||||||
Backend string `yaml:"backend"`
|
Backend string `yaml:"backend"`
|
||||||
Bucket string `yaml:"bucket"`
|
|
||||||
Prefix string `yaml:"prefix"`
|
|
||||||
S3 *StorageS3Config `yaml:"s3"`
|
S3 *StorageS3Config `yaml:"s3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,13 +232,6 @@ type ScriptoriumInputConfig struct {
|
|||||||
Required bool `yaml:"required"`
|
Required bool `yaml:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnalyzerConfig configures analyzer adapter settings.
|
|
||||||
type AnalyzerConfig struct {
|
|
||||||
BinaryPath string `yaml:"binary_path"`
|
|
||||||
Timeout string `yaml:"timeout"`
|
|
||||||
Artifacts ArtifactSettings `yaml:"artifacts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotificationConfig configures notification backend settings.
|
// NotificationConfig configures notification backend settings.
|
||||||
type NotificationConfig struct {
|
type NotificationConfig struct {
|
||||||
Backend string `yaml:"backend"`
|
Backend string `yaml:"backend"`
|
||||||
@@ -249,12 +239,6 @@ type NotificationConfig struct {
|
|||||||
Timeout string `yaml:"timeout"`
|
Timeout string `yaml:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArtifactSettings configures generated artifact selection and paths.
|
|
||||||
type ArtifactSettings struct {
|
|
||||||
OutputDir string `yaml:"output_dir"`
|
|
||||||
Types []string `yaml:"types"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionInputsConfig contains per-session input references.
|
// SessionInputsConfig contains per-session input references.
|
||||||
type SessionInputsConfig struct {
|
type SessionInputsConfig struct {
|
||||||
AudioDir string `yaml:"audio_dir"`
|
AudioDir string `yaml:"audio_dir"`
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ seriatim:
|
|||||||
binary: seriatim
|
binary: seriatim
|
||||||
audita:
|
audita:
|
||||||
binary: audita
|
binary: audita
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 15s
|
timeout: 15s
|
||||||
`,
|
`,
|
||||||
@@ -48,8 +46,6 @@ inputs:
|
|||||||
root: /tmp/narratio
|
root: /tmp/narratio
|
||||||
whisperx:
|
whisperx:
|
||||||
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 15s
|
timeout: 15s
|
||||||
`,
|
`,
|
||||||
@@ -67,8 +63,6 @@ inputs:
|
|||||||
name: "workspace root defaults when omitted",
|
name: "workspace root defaults when omitted",
|
||||||
pipelineYAML: `whisperx:
|
pipelineYAML: `whisperx:
|
||||||
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
analyzer:
|
|
||||||
timeout: 20m
|
|
||||||
notification:
|
notification:
|
||||||
timeout: 15s
|
timeout: 15s
|
||||||
`,
|
`,
|
||||||
@@ -97,6 +91,24 @@ inputs:
|
|||||||
`,
|
`,
|
||||||
wantLoadErr: "pipeline file",
|
wantLoadErr: "pipeline file",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "legacy analyzer section fails strict decode",
|
||||||
|
pipelineYAML: `workspace:
|
||||||
|
root: /tmp/narratio
|
||||||
|
whisperx:
|
||||||
|
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
||||||
|
analyzer:
|
||||||
|
timeout: 20m
|
||||||
|
`,
|
||||||
|
sessionYAML: `session_id: 2026-05-03
|
||||||
|
inputs:
|
||||||
|
audio_dir: ./audio
|
||||||
|
speakers_file: ./speakers.yml
|
||||||
|
autocorrect_file: ./autocorrect.yml
|
||||||
|
glossary_file: ./glossary.yml
|
||||||
|
`,
|
||||||
|
wantLoadErr: "strict decode failed",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unknown whisperx field fails",
|
name: "unknown whisperx field fails",
|
||||||
pipelineYAML: `workspace:
|
pipelineYAML: `workspace:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestScriptoriumLoadAndValidate(t *testing.T) {
|
func TestScriptoriumLoadAndValidate(t *testing.T) {
|
||||||
|
legacyPreviousSource := "previous_session_" + "artifact"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
scriptoriumYAML string
|
scriptoriumYAML string
|
||||||
@@ -94,7 +95,7 @@ func TestScriptoriumLoadAndValidate(t *testing.T) {
|
|||||||
wantValidateErr: "pipeline.scriptorium.timeout must be a valid duration",
|
wantValidateErr: "pipeline.scriptorium.timeout must be a valid duration",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "optional previous recap input is accepted",
|
name: "legacy previous session artifact source fails validation",
|
||||||
scriptoriumYAML: `scriptorium:
|
scriptoriumYAML: `scriptorium:
|
||||||
binary: scriptorium
|
binary: scriptorium
|
||||||
artifacts:
|
artifacts:
|
||||||
@@ -107,7 +108,7 @@ func TestScriptoriumLoadAndValidate(t *testing.T) {
|
|||||||
source: narratio.transcript.polished
|
source: narratio.transcript.polished
|
||||||
required: true
|
required: true
|
||||||
previous_recap:
|
previous_recap:
|
||||||
source: previous_session_artifact
|
source: ` + legacyPreviousSource + `
|
||||||
artifact: session_recap
|
artifact: session_recap
|
||||||
path: ""
|
path: ""
|
||||||
required: false
|
required: false
|
||||||
@@ -115,6 +116,7 @@ func TestScriptoriumLoadAndValidate(t *testing.T) {
|
|||||||
session_id: true
|
session_id: true
|
||||||
output_kind: session_recap
|
output_kind: session_recap
|
||||||
`,
|
`,
|
||||||
|
wantValidateErr: `pipeline.scriptorium.artifacts.session_recap.inputs.previous_recap.source "` + legacyPreviousSource + `" is unsupported`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "canonical previous-session source is accepted",
|
name: "canonical previous-session source is accepted",
|
||||||
|
|||||||
@@ -39,6 +39,49 @@ storage:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStorageLegacyTopLevelFieldsFailStrictDecode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
storageYAML string
|
||||||
|
wantField string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bucket",
|
||||||
|
storageYAML: `
|
||||||
|
storage:
|
||||||
|
bucket: my-dnd-archive
|
||||||
|
`,
|
||||||
|
wantField: "bucket",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prefix",
|
||||||
|
storageYAML: `
|
||||||
|
storage:
|
||||||
|
prefix: dnd
|
||||||
|
`,
|
||||||
|
wantField: "prefix",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
pipelineYAML := testPipelineBaseYAML + tt.storageYAML
|
||||||
|
pipelinePath, _ := writeConfigFiles(t, pipelineYAML, testSessionBaseYAML)
|
||||||
|
|
||||||
|
_, err := LoadPipeline(pipelinePath)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("LoadPipeline() error = nil, want strict decode error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "strict decode failed") {
|
||||||
|
t.Fatalf("LoadPipeline() error = %v, want strict decode failed", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.wantField) {
|
||||||
|
t.Fatalf("LoadPipeline() error = %v, want field %q", err, tt.wantField)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStorageS3CredentialEnvNamesLoadAndValidate(t *testing.T) {
|
func TestStorageS3CredentialEnvNamesLoadAndValidate(t *testing.T) {
|
||||||
pipelineYAML := testPipelineBaseYAML + `
|
pipelineYAML := testPipelineBaseYAML + `
|
||||||
storage:
|
storage:
|
||||||
|
|||||||
@@ -87,9 +87,6 @@ func validatePipeline(cfg *PipelineConfig) error {
|
|||||||
if err := validateScriptorium(cfg.Scriptorium); err != nil {
|
if err := validateScriptorium(cfg.Scriptorium); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := validateDuration("pipeline.analyzer.timeout", cfg.Analyzer.Timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := validateDuration("pipeline.notification.timeout", cfg.Notification.Timeout); err != nil {
|
if err := validateDuration("pipeline.notification.timeout", cfg.Notification.Timeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -720,8 +717,6 @@ func validateScriptoriumInputSource(artifactName, inputName, source string, conf
|
|||||||
|
|
||||||
func isStaticSupportedScriptoriumInputSource(source string) bool {
|
func isStaticSupportedScriptoriumInputSource(source string) bool {
|
||||||
switch source {
|
switch source {
|
||||||
case "previous_session_artifact":
|
|
||||||
return true
|
|
||||||
case "narratio.transcript.merged":
|
case "narratio.transcript.merged":
|
||||||
return true
|
return true
|
||||||
case "narratio.transcript.polished":
|
case "narratio.transcript.polished":
|
||||||
|
|||||||
@@ -649,15 +649,6 @@ func resolveScriptoriumInput(
|
|||||||
return "", false, nil, err
|
return "", false, nil, err
|
||||||
}
|
}
|
||||||
switch source {
|
switch source {
|
||||||
case "previous_session_artifact":
|
|
||||||
if strings.TrimSpace(inputCfg.Path) == "" {
|
|
||||||
return "", false, nil, nil
|
|
||||||
}
|
|
||||||
resolved := resolveInputPathForRead(paths, sessionDir, inputCfg.Path)
|
|
||||||
if err := requireFile(resolved, "scriptorium input "+inputName); err != nil {
|
|
||||||
return "", false, nil, nil
|
|
||||||
}
|
|
||||||
return resolved, true, nil, nil
|
|
||||||
default:
|
default:
|
||||||
resolved, err := artifacts.ResolveSessionArtifactWithCatalog(paths, m, source, runtimeCatalog)
|
resolved, err := artifacts.ResolveSessionArtifactWithCatalog(paths, m, source, runtimeCatalog)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ func TestAnalyzeRunStillSucceedsWhenRenderSucceeds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnalyzeOmitsOptionalPreviousRecapWhenUnavailable(t *testing.T) {
|
func TestAnalyzeOmitsOptionalCanonicalPreviousRecapWhenUnavailable(t *testing.T) {
|
||||||
env, m, fake := setupAnalyzeEnv(t)
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
paths := sessionPathsForEnv(env, m.SessionID)
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
@@ -236,9 +236,7 @@ func TestAnalyzeOmitsOptionalPreviousRecapWhenUnavailable(t *testing.T) {
|
|||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
"previous_recap": {
|
"previous_recap": {
|
||||||
Source: "previous_session_artifact",
|
Source: "narratio.previous_session.artifact.session_recap",
|
||||||
Artifact: "session_recap",
|
|
||||||
Path: "",
|
|
||||||
Required: false,
|
Required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -343,12 +341,12 @@ func (r *orderedScriptoriumRunner) RunArtifact(_ context.Context, req scriptoriu
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnalyzeIncludesPreviousRecapWhenConfiguredAndAvailable(t *testing.T) {
|
func TestAnalyzeIncludesCanonicalPreviousRecapWhenPreparedCacheExists(t *testing.T) {
|
||||||
env, m, fake := setupAnalyzeEnv(t)
|
env, m, fake := setupAnalyzeEnv(t)
|
||||||
paths := sessionPathsForEnv(env, m.SessionID)
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
|
|
||||||
previousRecapPath := filepath.Join(filepath.Dir(env.Config.SessionPath), "previous", "session_recap.md")
|
previousRecapPath := artifacts.SessionPreviousArtifactPath(paths, "artifacts/session_recap.md")
|
||||||
writeAnalyzeFile(t, previousRecapPath, "previous recap\n")
|
writeAnalyzeFile(t, previousRecapPath, "previous recap\n")
|
||||||
|
|
||||||
env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = config.ScriptoriumArtifactConfig{
|
env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = config.ScriptoriumArtifactConfig{
|
||||||
@@ -363,9 +361,7 @@ func TestAnalyzeIncludesPreviousRecapWhenConfiguredAndAvailable(t *testing.T) {
|
|||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
"previous_recap": {
|
"previous_recap": {
|
||||||
Source: "previous_session_artifact",
|
Source: "narratio.previous_session.artifact.session_recap",
|
||||||
Artifact: "session_recap",
|
|
||||||
Path: "./previous/session_recap.md",
|
|
||||||
Required: false,
|
Required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -384,7 +380,7 @@ func TestAnalyzeIncludesPreviousRecapWhenConfiguredAndAvailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnalyzeFailsWhenRequiredPreviousRecapMissing(t *testing.T) {
|
func TestAnalyzeFailsWhenRequiredCanonicalPreviousRecapMissing(t *testing.T) {
|
||||||
env, m, _ := setupAnalyzeEnv(t)
|
env, m, _ := setupAnalyzeEnv(t)
|
||||||
paths := sessionPathsForEnv(env, m.SessionID)
|
paths := sessionPathsForEnv(env, m.SessionID)
|
||||||
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "processed.json"), `{"segments":[]}`)
|
||||||
@@ -399,8 +395,7 @@ func TestAnalyzeFailsWhenRequiredPreviousRecapMissing(t *testing.T) {
|
|||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
"previous_recap": {
|
"previous_recap": {
|
||||||
Source: "previous_session_artifact",
|
Source: "narratio.previous_session.artifact.session_recap",
|
||||||
Path: "./missing/previous_recap.md",
|
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -410,8 +405,8 @@ func TestAnalyzeFailsWhenRequiredPreviousRecapMissing(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatal("expected error, got nil")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), `required input "previous_recap"`) {
|
if !strings.Contains(err.Error(), "run narratio run-stage --force prepare") {
|
||||||
t.Fatalf("error = %q, want required input context", err.Error())
|
t.Fatalf("error = %q, want guidance to run force prepare", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,9 +1203,7 @@ func setupAnalyzeEnv(t *testing.T) (*Env, *manifest.Manifest, *scriptorium.FakeR
|
|||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
"previous_recap": {
|
"previous_recap": {
|
||||||
Source: "previous_session_artifact",
|
Source: "narratio.previous_session.artifact.session_recap",
|
||||||
Artifact: "session_recap",
|
|
||||||
Path: "",
|
|
||||||
Required: false,
|
Required: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/adapters/analyzer"
|
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/adapters/audita"
|
"gitea.maximumdirect.net/eric/narratio/internal/adapters/audita"
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/adapters/notify"
|
"gitea.maximumdirect.net/eric/narratio/internal/adapters/notify"
|
||||||
"gitea.maximumdirect.net/eric/narratio/internal/adapters/scriptorium"
|
"gitea.maximumdirect.net/eric/narratio/internal/adapters/scriptorium"
|
||||||
@@ -28,7 +27,6 @@ type Env struct {
|
|||||||
Seriatim seriatim.Runner
|
Seriatim seriatim.Runner
|
||||||
Audita audita.Runner
|
Audita audita.Runner
|
||||||
Scriptorium scriptorium.Runner
|
Scriptorium scriptorium.Runner
|
||||||
Analyzer analyzer.Runner
|
|
||||||
Storage storage.Backend
|
Storage storage.Backend
|
||||||
ObjectStore storage.ObjectStore
|
ObjectStore storage.ObjectStore
|
||||||
Notifier notify.Sender
|
Notifier notify.Sender
|
||||||
|
|||||||
Reference in New Issue
Block a user