444 lines
14 KiB
Markdown
444 lines
14 KiB
Markdown
# Implementation Plan: Checkpoint 5 Seriatim Input Module
|
|
|
|
## Status
|
|
|
|
This is a staged implementation plan for
|
|
[`5-seriatim-input-module.md`](5-seriatim-input-module.md). It is intended for
|
|
an LLM coding agent to follow stage by stage.
|
|
|
|
This plan implements only checkpoint 5. Do not add D&D extraction, real domain
|
|
prompts or schemas, LLM extraction calls, a `notarius run` command, broad
|
|
Seriatim schema support, or transcript-specific behavior in core framework
|
|
packages in this checkpoint.
|
|
|
|
## Policy Context
|
|
|
|
Follow:
|
|
|
|
- [`docs/policy/architecture.md`](../policy/architecture.md)
|
|
- [`docs/policy/documentation.md`](../policy/documentation.md)
|
|
|
|
Required boundaries:
|
|
|
|
- keep Seriatim JSON schema details inside `internal/modules/input/seriatim`;
|
|
- keep core source, runner, pipeline, extractor, validator, LLM, and config
|
|
packages source-agnostic and domain-agnostic;
|
|
- do not add transcript-specific typed fields to `SourceDocument`,
|
|
`SourceUnit`, runner contracts, or pipeline contracts;
|
|
- preserve transcript-specific values only as source metadata conventions;
|
|
- register the input module through the existing input adapter registry instead
|
|
of adding ad hoc conditionals;
|
|
- use flat capability strings in module metadata;
|
|
- keep future or planned behavior in `docs/roadmap/` until implemented.
|
|
|
|
## Global Implementation Decisions
|
|
|
|
- Add no new third-party dependency. Use `encoding/json` with
|
|
`Decoder.UseNumber` for Seriatim JSON parsing.
|
|
- Use `seriatim` as the stable input adapter key.
|
|
- Put all concrete Seriatim input code under
|
|
`internal/modules/input/seriatim`.
|
|
- Expose a small module API:
|
|
|
|
```go
|
|
const Key = "seriatim"
|
|
|
|
func New() *Adapter
|
|
func ModuleSpec() pipeline.ModuleSpec
|
|
func Register(registry *pipeline.InputAdapterRegistry) error
|
|
```
|
|
|
|
- `ModuleSpec()` must return stage `pipeline.StageInput`, no required
|
|
capabilities, and these provided capabilities:
|
|
`source.transcript`, `transcript.speaker`, and `transcript.timestamps`.
|
|
- The Seriatim adapter should satisfy `contracts.InputAdapter`.
|
|
- Use `source.SourceDocument.Kind = "transcript"`.
|
|
- Use `source.SourceDocument.Format =
|
|
"application/vnd.seriatim.minimal+json"`.
|
|
- Use `source.SourceUnit.Kind = "transcript_segment"`.
|
|
- Compute `SourceDocument.Digest` from the exact raw input bytes as
|
|
`sha256:<hex>`.
|
|
- Resolve `SourceDocument.ID` in this order:
|
|
1. trimmed `contracts.ParseRequest.SourceID`, if non-empty;
|
|
2. trimmed string `metadata.id`, if present and non-empty;
|
|
3. trimmed string `metadata.source_id`, if present and non-empty;
|
|
4. deterministic fallback `seriatim:<first-16-hex-chars-of-raw-sha256>`.
|
|
- Segment IDs become source unit IDs exactly after validation. Reject segment
|
|
IDs with leading or trailing whitespace rather than silently rewriting them.
|
|
- Copy top-level Seriatim `metadata` into `SourceDocument.Metadata`.
|
|
- Store segment `speaker`, `start`, and `end` in `SourceUnit.Metadata` under
|
|
keys with those exact names.
|
|
- Store `start` and `end` as `json.Number` values so JSON serialization remains
|
|
numeric and the original decimal representation is preserved.
|
|
- Require top-level `metadata` to be present and be an object, but do not
|
|
require any specific metadata keys in checkpoint 5.
|
|
- Require top-level `segments` to be present and contain at least one segment.
|
|
- Reject unknown or extra JSON fields only if they prevent parsing the minimal
|
|
shape. Otherwise ignore them so the module can tolerate compatible Seriatim
|
|
additions.
|
|
- Return module-specific errors prefixed with useful Seriatim context, for
|
|
example `seriatim input: segment "s1" text must not be empty`.
|
|
- Keep examples free of private transcript content. Use synthetic fixture text.
|
|
|
|
## Stage 1: Seriatim Package Skeleton And External Model
|
|
|
|
### Goal
|
|
|
|
Create the Seriatim input module package, define the module-local JSON model,
|
|
and add registry-facing module metadata without changing framework contracts.
|
|
|
|
### Files To Add
|
|
|
|
- `internal/modules/input/seriatim/adapter.go`
|
|
- `internal/modules/input/seriatim/model.go`
|
|
- `internal/modules/input/seriatim/metadata.go`
|
|
- `internal/modules/input/seriatim/registry_test.go`
|
|
|
|
### Required API
|
|
|
|
Add:
|
|
|
|
```go
|
|
package seriatim
|
|
|
|
const Key = "seriatim"
|
|
|
|
const (
|
|
DocumentKind = "transcript"
|
|
UnitKind = "transcript_segment"
|
|
Format = "application/vnd.seriatim.minimal+json"
|
|
)
|
|
|
|
const (
|
|
MetadataSpeaker = "speaker"
|
|
MetadataStart = "start"
|
|
MetadataEnd = "end"
|
|
)
|
|
|
|
type Adapter struct{}
|
|
|
|
func New() *Adapter
|
|
func (a *Adapter) Key() string
|
|
func (a *Adapter) Parse(ctx context.Context, req contracts.ParseRequest) (*source.SourceDocument, error)
|
|
func ModuleSpec() pipeline.ModuleSpec
|
|
func Register(registry *pipeline.InputAdapterRegistry) error
|
|
```
|
|
|
|
Add typed metadata helpers:
|
|
|
|
```go
|
|
func Speaker(unit source.SourceUnit) (string, bool)
|
|
func Start(unit source.SourceUnit) (json.Number, bool)
|
|
func End(unit source.SourceUnit) (json.Number, bool)
|
|
```
|
|
|
|
### Seriatim JSON Shape
|
|
|
|
Define module-local structs for the minimal external shape:
|
|
|
|
```go
|
|
type transcript struct {
|
|
Metadata map[string]any `json:"metadata"`
|
|
Segments []segment `json:"segments"`
|
|
}
|
|
|
|
type segment struct {
|
|
ID string `json:"id"`
|
|
Start json.Number `json:"start"`
|
|
End json.Number `json:"end"`
|
|
Speaker string `json:"speaker"`
|
|
Text string `json:"text"`
|
|
}
|
|
```
|
|
|
|
Use an internal decode helper based on `json.NewDecoder(bytes.NewReader(raw))`
|
|
and `UseNumber`.
|
|
|
|
### Required Behavior
|
|
|
|
- `New()` returns a non-nil adapter.
|
|
- `Adapter.Key()` returns `Key`.
|
|
- `ModuleSpec()` returns defensive slices and the capability set listed in the
|
|
global decisions.
|
|
- `Register()` calls `InputAdapterRegistry.RegisterWithSpec(ModuleSpec(), ...)`.
|
|
- `Register(nil)` returns an error from the registry path rather than panicking.
|
|
- Keep external JSON structs unexported.
|
|
|
|
### Required Tests
|
|
|
|
- `New()` returns an adapter whose key is `seriatim`.
|
|
- `ModuleSpec()` uses input stage and declares the required provided
|
|
capabilities.
|
|
- `Register()` makes the adapter buildable from an `InputAdapterRegistry`.
|
|
- Registry lookup returns the Seriatim module spec.
|
|
|
|
### Validation
|
|
|
|
Run:
|
|
|
|
```sh
|
|
gofmt -w internal/modules/input/seriatim
|
|
go test ./internal/modules/input/seriatim
|
|
go test ./...
|
|
```
|
|
|
|
## Stage 2: Parse, Validate, And Map To SourceDocument
|
|
|
|
### Goal
|
|
|
|
Implement the Seriatim parser and mapper from minimal Seriatim JSON into the
|
|
generic source model.
|
|
|
|
### Files To Add Or Update
|
|
|
|
- `internal/modules/input/seriatim/adapter.go`
|
|
- `internal/modules/input/seriatim/model.go`
|
|
- `internal/modules/input/seriatim/adapter_test.go`
|
|
- `internal/modules/input/seriatim/testdata/valid_minimal.json`
|
|
- `internal/modules/input/seriatim/testdata/duplicate_segment_id.json`
|
|
|
|
### Required Validation
|
|
|
|
Reject:
|
|
|
|
- nil or canceled context before parsing;
|
|
- empty raw input;
|
|
- malformed JSON;
|
|
- valid JSON with trailing non-whitespace data;
|
|
- missing, null, or non-object top-level `metadata`;
|
|
- missing, null, empty, or non-array top-level `segments`;
|
|
- segment IDs that are empty after trimming;
|
|
- segment IDs with leading or trailing whitespace;
|
|
- duplicate segment IDs;
|
|
- missing or empty `speaker`;
|
|
- missing, empty, non-numeric, negative, or non-finite `start`;
|
|
- missing, empty, non-numeric, negative, or non-finite `end`;
|
|
- segments where `end < start`;
|
|
- missing or empty `text`.
|
|
|
|
The parser may preserve leading and trailing whitespace in segment text as long
|
|
as the text is not empty after trimming.
|
|
|
|
### Mapping Rules
|
|
|
|
- `segment.id` becomes `SourceUnit.ID`.
|
|
- `segment.text` becomes `SourceUnit.Text`.
|
|
- `speaker`, `start`, and `end` become unit metadata under the exact keys
|
|
defined in stage 1.
|
|
- The document metadata is a shallow copy of top-level Seriatim metadata.
|
|
- The document digest is based on raw input bytes, not normalized JSON.
|
|
- Call `source.ValidateDocument` before returning the document and wrap any
|
|
validation failure with Seriatim context.
|
|
|
|
### Required Tests
|
|
|
|
Add tests for:
|
|
|
|
- valid minimal transcript parses to a source document with expected ID, kind,
|
|
format, digest, units, and metadata;
|
|
- `ParseRequest.SourceID` overrides metadata-derived IDs;
|
|
- fallback document ID is deterministic and has prefix `seriatim:`;
|
|
- malformed JSON returns an actionable Seriatim parse error;
|
|
- missing metadata is rejected;
|
|
- missing or empty segments is rejected;
|
|
- duplicate segment IDs are rejected;
|
|
- empty segment text is rejected;
|
|
- missing speaker is rejected;
|
|
- invalid timestamp values are rejected;
|
|
- `end < start` is rejected;
|
|
- typed metadata helpers return the expected speaker and timestamp values;
|
|
- a `source.SourceRef` using the first and last generated unit IDs validates
|
|
with `source.ValidateRef`.
|
|
|
|
### Validation
|
|
|
|
Run:
|
|
|
|
```sh
|
|
gofmt -w internal/modules/input/seriatim
|
|
go test ./internal/modules/input/seriatim
|
|
go test ./...
|
|
```
|
|
|
|
## Stage 3: Pipeline Resolution And Config Compatibility
|
|
|
|
### Goal
|
|
|
|
Prove the Seriatim input module participates in pipeline-profile resolution and
|
|
capability validation through existing registries and config loading.
|
|
|
|
### Files To Add Or Update
|
|
|
|
- `internal/modules/input/seriatim/config_test.go`
|
|
- `internal/modules/input/seriatim/testdata/pipeline.yml`
|
|
|
|
### Required Test Catalog
|
|
|
|
Build a test-only module catalog with:
|
|
|
|
- Seriatim input registered through `seriatim.Register`;
|
|
- a fake chunker requiring `source.transcript` and providing `chunks`;
|
|
- a fake extractor requiring `chunks`, `transcript.speaker`, and
|
|
`transcript.timestamps`, and providing `fake.artifacts`;
|
|
- `pipeline.AppendOrderMerger` registered as `appendorder`, requiring
|
|
`fake.artifacts`;
|
|
- `pipeline.NoopNormalizer` registered as `noop`;
|
|
- a fake `json` output encoder registered as output stage.
|
|
|
|
Do not add real extract, chunk, normalize, or output modules for this checkpoint.
|
|
|
|
### YAML Fixture
|
|
|
|
Use a synthetic pipeline fixture shaped like:
|
|
|
|
```yaml
|
|
version: 1
|
|
pipelines:
|
|
seriatim-fixture:
|
|
input: seriatim
|
|
chunk: fake/chunk
|
|
artifacts:
|
|
events:
|
|
extract: fake/extract
|
|
merge: appendorder
|
|
normalize: noop
|
|
output: json
|
|
```
|
|
|
|
The default LLM profile supplied by `config.Default()` is sufficient. Do not
|
|
add real provider settings to this fixture.
|
|
|
|
### Required Tests
|
|
|
|
- `config.ParseFileConfigYAML` and `Config.ApplyFileConfig` load the fixture.
|
|
- `Config.Resolve` succeeds with pipeline ID `seriatim-fixture` and the
|
|
test-only catalog.
|
|
- The resolved pipeline input module is `seriatim`.
|
|
- The resolved pipeline digest is non-empty and stable across repeated
|
|
resolution.
|
|
- Removing `transcript.timestamps` from the Seriatim module spec in the
|
|
test-only catalog causes resolution to fail with a missing capability error.
|
|
- Selecting an unknown `--only` lane still fails through existing resolution
|
|
behavior.
|
|
|
|
### Validation
|
|
|
|
Run:
|
|
|
|
```sh
|
|
gofmt -w internal/modules/input/seriatim
|
|
go test ./internal/modules/input/seriatim
|
|
go test ./internal/core/config
|
|
go test ./...
|
|
```
|
|
|
|
## Stage 4: Runner Integration With Fake Downstream Stages
|
|
|
|
### Goal
|
|
|
|
Prove real Seriatim input can flow through the existing runner into fake
|
|
downstream stages while preserving source-unit IDs and metadata.
|
|
|
|
### Files To Add Or Update
|
|
|
|
- `internal/modules/input/seriatim/runner_test.go`
|
|
|
|
### Required Behavior
|
|
|
|
Use the same Seriatim fixture from stage 2 and a resolved pipeline from stage 3.
|
|
Register fake downstream stages only inside the test.
|
|
|
|
The fake extractor should:
|
|
|
|
- inspect the received `SourceDocument` and `SourceChunk`;
|
|
- assert that unit IDs match Seriatim segment IDs;
|
|
- assert that speaker and timestamp metadata are present;
|
|
- return one generic artifact candidate with a source reference pointing at
|
|
existing Seriatim-derived unit IDs.
|
|
|
|
The test should then assert:
|
|
|
|
- `Runner.Run` succeeds;
|
|
- the manifest records input module `seriatim`;
|
|
- the manifest source digest equals the parsed document digest;
|
|
- approved artifacts preserve valid source references;
|
|
- no transcript-specific type has been added outside the module.
|
|
|
|
### Required Tests
|
|
|
|
- successful runner execution from Seriatim JSON through fake chunk, extract,
|
|
merge, normalize, and output stages;
|
|
- runner failure when the Seriatim adapter returns an invalid source document,
|
|
using a malformed fixture or test input;
|
|
- validation of the fake candidate's source reference with
|
|
`source.ValidateRef`.
|
|
|
|
### Validation
|
|
|
|
Run:
|
|
|
|
```sh
|
|
gofmt -w internal/modules/input/seriatim
|
|
go test ./internal/modules/input/seriatim
|
|
go test ./internal/framework/pipeline
|
|
go test ./...
|
|
```
|
|
|
|
## Stage 5: Documentation And Final Verification
|
|
|
|
### Goal
|
|
|
|
Document the implemented Seriatim integration contract once the module exists,
|
|
without describing unimplemented D&D extraction or run-command behavior.
|
|
|
|
### Files To Add Or Update
|
|
|
|
- `docs/integrations/seriatim.md`
|
|
- `docs/roadmap/5-seriatim-input-module.md`
|
|
|
|
### Required Documentation
|
|
|
|
Create `docs/integrations/seriatim.md` as implemented-behavior documentation
|
|
with:
|
|
|
|
- accepted minimal JSON shape;
|
|
- required fields and validation rules;
|
|
- mapping from Seriatim fields to `SourceDocument` and `SourceUnit`;
|
|
- metadata key conventions for `speaker`, `start`, and `end`;
|
|
- capability strings declared by the module;
|
|
- note that broader Seriatim schema variants are not yet supported.
|
|
|
|
Update `docs/roadmap/5-seriatim-input-module.md` only if implementation
|
|
reveals a real scope or policy correction. Keep future D&D extraction behavior
|
|
out of the integration doc.
|
|
|
|
### Final Validation
|
|
|
|
Run:
|
|
|
|
```sh
|
|
gofmt -w internal/modules/input/seriatim
|
|
go test ./...
|
|
go build ./cmd/notarius
|
|
rm -f ./notarius
|
|
```
|
|
|
|
### Done Criteria
|
|
|
|
- `go test ./...` passes.
|
|
- `go build ./cmd/notarius` passes.
|
|
- Seriatim minimal transcript JSON maps into `SourceDocument`.
|
|
- Unit IDs are stable and validate in source references.
|
|
- Transcript fields do not appear in core runner contracts.
|
|
- The input module is selectable through the input registry and pipeline-profile
|
|
resolution.
|
|
- The input module declares transcript-oriented flat capabilities for pipeline
|
|
validation.
|
|
- Tests prove transcript-specific assumptions are isolated to
|
|
`internal/modules/input/seriatim`.
|
|
|
|
## Open Questions
|
|
|
|
None. This plan chooses the checkpoint-5 behavior needed to implement the
|
|
feature without requiring additional product decisions.
|