Add an initial staged implementation prompt fro the core contracts and skeleton
This commit is contained in:
@@ -32,79 +32,26 @@ Out of scope:
|
||||
- diagnostics run directory;
|
||||
- production config loading.
|
||||
|
||||
## Proposed Stages
|
||||
## Target End State
|
||||
|
||||
### Stage 1: Bootstrap Go Project
|
||||
The repository should contain a compileable Go application shell and stable core
|
||||
contract packages:
|
||||
|
||||
Create:
|
||||
- `cmd/notarius` provides the executable entrypoint.
|
||||
- `internal/cli` provides a minimal CLI shell.
|
||||
- `internal/core/source` defines generic source documents, source units, source
|
||||
references, and source validation helpers.
|
||||
- `internal/core/artifacts` defines extractor-neutral artifact candidate,
|
||||
approved artifact, rejected artifact, and run manifest types.
|
||||
- `internal/framework/contracts` defines the adapter, extractor, validator, and
|
||||
structured LLM interfaces used by later checkpoints.
|
||||
|
||||
- `go.mod`;
|
||||
- `cmd/notarius/main.go`;
|
||||
- `internal/cli`;
|
||||
- a minimal CLI command surface that compiles.
|
||||
The contracts should be proven with fake implementations in tests. Those tests
|
||||
should demonstrate composition without real source adapters, real extractors,
|
||||
LLM provider calls, prompt assets, or diagnostics infrastructure.
|
||||
|
||||
Expected validation:
|
||||
|
||||
```sh
|
||||
go test ./...
|
||||
go build ./cmd/notarius
|
||||
```
|
||||
|
||||
### Stage 2: Source Core Types
|
||||
|
||||
Create `internal/core/source`.
|
||||
|
||||
Initial types:
|
||||
|
||||
- `SourceDocument`;
|
||||
- `SourceUnit`;
|
||||
- `SourceRef`;
|
||||
- source validation helpers for document ID, ordered units, unique unit IDs, and
|
||||
non-empty text.
|
||||
|
||||
Keep the model generic. Do not introduce transcript-specific field names into
|
||||
core source types.
|
||||
|
||||
### Stage 3: Artifact Core Types
|
||||
|
||||
Create `internal/core/artifacts`.
|
||||
|
||||
Initial types:
|
||||
|
||||
- artifact candidate;
|
||||
- approved artifact envelope;
|
||||
- rejected artifact record;
|
||||
- run manifest;
|
||||
- source-reference-bearing helper interfaces or conventions if useful.
|
||||
|
||||
Keep the artifact model extractor-neutral. D&D-specific fields should wait until
|
||||
the D&D spells extractor checkpoint.
|
||||
|
||||
### Stage 4: Framework Contract Types
|
||||
|
||||
Create `internal/framework/contracts`.
|
||||
|
||||
Initial contracts:
|
||||
|
||||
- `InputAdapter`;
|
||||
- `Extractor`;
|
||||
- `Validator`;
|
||||
- structured LLM client interface placeholder;
|
||||
- parse, extraction, and validation request/response types.
|
||||
|
||||
Interfaces should be small and should depend on core source/artifact types, not
|
||||
on concrete adapter or extractor packages.
|
||||
|
||||
### Stage 5: Contract Tests With Fakes
|
||||
|
||||
Add tests using fake adapter, extractor, and validator implementations.
|
||||
|
||||
These tests should prove:
|
||||
|
||||
- fake components can satisfy the interfaces;
|
||||
- source documents can flow into extraction requests;
|
||||
- artifact candidates can flow into validation requests;
|
||||
- the contracts are not forcing transcript or D&D assumptions.
|
||||
Implementation staging belongs in
|
||||
[`implementation.md`](implementation.md).
|
||||
|
||||
## Done Criteria
|
||||
|
||||
|
||||
534
docs/roadmap/implementation.md
Normal file
534
docs/roadmap/implementation.md
Normal file
@@ -0,0 +1,534 @@
|
||||
# Implementation Plan: Checkpoint 1 Core Contracts And Skeleton
|
||||
|
||||
## Status
|
||||
|
||||
This is a staged implementation plan for
|
||||
[`1-core-contracts-and-skeleton.md`](1-core-contracts-and-skeleton.md). It is
|
||||
intended for an LLM coding agent to follow stage by stage.
|
||||
|
||||
This plan implements only checkpoint 1. Do not implement real input adapters,
|
||||
real extractors, LLM provider clients, prompt assets, response schema registries,
|
||||
diagnostics run directories, or production config loading in this pass.
|
||||
|
||||
## Policy Context
|
||||
|
||||
Follow:
|
||||
|
||||
- [`docs/policy/architecture.md`](../policy/architecture.md)
|
||||
- [`docs/policy/documentation.md`](../policy/documentation.md)
|
||||
|
||||
Required boundaries:
|
||||
|
||||
- core packages must stay source-agnostic and domain-agnostic;
|
||||
- transcript-specific concepts must not appear in core or framework contracts;
|
||||
- D&D concepts must not appear in core or framework contracts;
|
||||
- source references must target generic source units;
|
||||
- planned behavior outside this checkpoint must remain in roadmap docs.
|
||||
|
||||
## Global Implementation Decisions
|
||||
|
||||
- Use module path `gitea.maximumdirect.net/eric/notarius`.
|
||||
- Use Go `1.24.0`, matching the nearby Audita project.
|
||||
- Add no third-party dependencies in checkpoint 1.
|
||||
- Keep CLI behavior minimal: root help/usage and unknown-command handling only.
|
||||
- Use package names exactly as listed in this plan unless implementation reveals
|
||||
a compile-time conflict.
|
||||
- Use `encoding/json.RawMessage` for generic artifact payloads.
|
||||
- Store contract tests close to the contracts they exercise.
|
||||
- Run `gofmt` on all Go files before validation.
|
||||
|
||||
## Stage 1: Bootstrap Go Project And CLI Shell
|
||||
|
||||
### Goal
|
||||
|
||||
Create a compileable Go application shell with a minimal CLI entrypoint.
|
||||
|
||||
### Files To Add
|
||||
|
||||
- `go.mod`
|
||||
- `cmd/notarius/main.go`
|
||||
- `internal/cli/run.go`
|
||||
- `internal/cli/run_test.go`
|
||||
|
||||
### Required Implementation
|
||||
|
||||
Create `go.mod`:
|
||||
|
||||
```go
|
||||
module gitea.maximumdirect.net/eric/notarius
|
||||
|
||||
go 1.24.0
|
||||
```
|
||||
|
||||
Create `cmd/notarius/main.go`:
|
||||
|
||||
- import `os`;
|
||||
- import `gitea.maximumdirect.net/eric/notarius/internal/cli`;
|
||||
- call `os.Exit(cli.Run(os.Args[1:], os.Stdout, os.Stderr))`.
|
||||
|
||||
Create `internal/cli.Run(args []string, stdout, stderr io.Writer) int`.
|
||||
|
||||
Initial behavior:
|
||||
|
||||
- `notarius`, `notarius help`, `notarius --help`, and `notarius -h` write root
|
||||
usage to `stdout` and return `0`;
|
||||
- unknown commands write `notarius: unknown command "<cmd>"` plus usage to
|
||||
`stderr` and return `2`;
|
||||
- no `extract` command is implemented in checkpoint 1.
|
||||
|
||||
Root usage should be short and should not document unimplemented behavior as if
|
||||
it exists. Acceptable text:
|
||||
|
||||
```text
|
||||
Usage:
|
||||
notarius help
|
||||
```
|
||||
|
||||
### Required Tests
|
||||
|
||||
In `internal/cli/run_test.go`, cover:
|
||||
|
||||
- no args returns `0` and writes usage to stdout;
|
||||
- `help`, `--help`, and `-h` return `0`;
|
||||
- unknown command returns `2`, writes to stderr, and does not write meaningful
|
||||
stdout.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
gofmt -w cmd internal
|
||||
go test ./...
|
||||
go build ./cmd/notarius
|
||||
```
|
||||
|
||||
## Stage 2: Core Source Model
|
||||
|
||||
### Goal
|
||||
|
||||
Add the generic source-document model and validation helpers.
|
||||
|
||||
### Files To Add
|
||||
|
||||
- `internal/core/source/source.go`
|
||||
- `internal/core/source/validation.go`
|
||||
- `internal/core/source/source_test.go`
|
||||
|
||||
### Required Types
|
||||
|
||||
Create package `source`.
|
||||
|
||||
Define:
|
||||
|
||||
```go
|
||||
type SourceDocument struct {
|
||||
ID string `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
Format string `json:"format"`
|
||||
Digest string `json:"digest"`
|
||||
Units []SourceUnit `json:"units"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type SourceUnit struct {
|
||||
ID string `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
Text string `json:"text"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type SourceRef struct {
|
||||
SourceID string `json:"source_id"`
|
||||
StartUnitID string `json:"start_unit_id"`
|
||||
EndUnitID string `json:"end_unit_id"`
|
||||
}
|
||||
```
|
||||
|
||||
### Required Helpers
|
||||
|
||||
Implement:
|
||||
|
||||
```go
|
||||
func ValidateDocument(doc *SourceDocument) error
|
||||
func ValidateRef(doc *SourceDocument, ref SourceRef) error
|
||||
func UnitIndex(doc *SourceDocument, unitID string) (int, bool)
|
||||
```
|
||||
|
||||
Validation rules:
|
||||
|
||||
- document must be non-nil;
|
||||
- document `ID`, `Kind`, `Format`, and `Digest` must be non-empty after
|
||||
trimming whitespace;
|
||||
- document must contain at least one unit;
|
||||
- each unit `ID`, `Kind`, and `Text` must be non-empty after trimming
|
||||
whitespace;
|
||||
- unit IDs must be unique within a document;
|
||||
- `ValidateRef` requires non-empty `SourceID`, `StartUnitID`, and `EndUnitID`;
|
||||
- `ValidateRef` requires `ref.SourceID == doc.ID`;
|
||||
- start and end unit IDs must exist;
|
||||
- start unit index must be less than or equal to end unit index.
|
||||
|
||||
Do not add transcript-specific fields or helpers.
|
||||
|
||||
### Error Style
|
||||
|
||||
Return ordinary Go errors with actionable field context, such as:
|
||||
|
||||
- `source document id must not be empty`;
|
||||
- `source unit[2].id must not be empty`;
|
||||
- `source unit id "u1" is duplicated`;
|
||||
- `source ref start_unit_id "u9" was not found`.
|
||||
|
||||
### Required Tests
|
||||
|
||||
Cover:
|
||||
|
||||
- valid document;
|
||||
- nil document;
|
||||
- missing document fields;
|
||||
- empty units;
|
||||
- missing unit fields;
|
||||
- duplicate unit IDs;
|
||||
- valid source reference;
|
||||
- source ID mismatch;
|
||||
- missing source-ref unit ID;
|
||||
- reversed source-ref unit order.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
gofmt -w internal/core/source
|
||||
go test ./internal/core/source
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Stage 3: Core Artifact Model
|
||||
|
||||
### Goal
|
||||
|
||||
Add extractor-neutral artifact and manifest types.
|
||||
|
||||
### Files To Add
|
||||
|
||||
- `internal/core/artifacts/artifacts.go`
|
||||
- `internal/core/artifacts/artifacts_test.go`
|
||||
|
||||
### Required Types
|
||||
|
||||
Create package `artifacts`.
|
||||
|
||||
Define:
|
||||
|
||||
```go
|
||||
type Candidate struct {
|
||||
Index int `json:"index"`
|
||||
ExtractorKey string `json:"extractor_key"`
|
||||
ArtifactType string `json:"artifact_type"`
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
SourceRefs []source.SourceRef `json:"source_refs,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Artifact struct {
|
||||
ExtractorKey string `json:"extractor_key"`
|
||||
ArtifactType string `json:"artifact_type"`
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
SourceRefs []source.SourceRef `json:"source_refs,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type RejectedArtifact struct {
|
||||
Candidate Candidate `json:"candidate"`
|
||||
ValidatorName string `json:"validator_name"`
|
||||
ReasonCode string `json:"reason_code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type RunManifest struct {
|
||||
RunID string `json:"run_id,omitempty"`
|
||||
InputAdapter string `json:"input_adapter,omitempty"`
|
||||
SourceDigests []string `json:"source_digests,omitempty"`
|
||||
Extractors []string `json:"extractors,omitempty"`
|
||||
SchemaVersion string `json:"schema_version,omitempty"`
|
||||
ValidationStatus string `json:"validation_status,omitempty"`
|
||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
Imports should include:
|
||||
|
||||
- `encoding/json`;
|
||||
- `time`;
|
||||
- `gitea.maximumdirect.net/eric/notarius/internal/core/source`.
|
||||
|
||||
Add:
|
||||
|
||||
```go
|
||||
func ArtifactFromCandidate(candidate Candidate) Artifact
|
||||
```
|
||||
|
||||
This helper should copy candidate fields into an approved artifact. It may share
|
||||
immutable `json.RawMessage` bytes at this checkpoint, but copying slices is
|
||||
preferred where cheap.
|
||||
|
||||
### Required Tests
|
||||
|
||||
Cover:
|
||||
|
||||
- `ArtifactFromCandidate` preserves extractor key, artifact type, schema
|
||||
version, payload, source refs, and metadata;
|
||||
- JSON marshaling uses the expected field names;
|
||||
- empty optional manifest fields are omitted.
|
||||
|
||||
Do not add D&D-specific payload structs.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
gofmt -w internal/core/artifacts
|
||||
go test ./internal/core/artifacts
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Stage 4: Framework Contract Types
|
||||
|
||||
### Goal
|
||||
|
||||
Define the interfaces and request/response types later checkpoints will build
|
||||
against.
|
||||
|
||||
### Files To Add
|
||||
|
||||
- `internal/framework/contracts/contracts.go`
|
||||
- `internal/framework/contracts/contracts_test.go`
|
||||
|
||||
### Required Types And Interfaces
|
||||
|
||||
Create package `contracts`.
|
||||
|
||||
Define LLM-neutral message and completion types:
|
||||
|
||||
```go
|
||||
type LLMMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type StructuredCompletionRequest struct {
|
||||
StageName string `json:"stage_name"`
|
||||
Messages []LLMMessage `json:"messages"`
|
||||
Model string `json:"model,omitempty"`
|
||||
ResponseSchemaName string `json:"response_schema_name,omitempty"`
|
||||
ResponseSchema json.RawMessage `json:"response_schema,omitempty"`
|
||||
}
|
||||
|
||||
type StructuredCompletionResponse struct {
|
||||
Content json.RawMessage `json:"content"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
PromptTokens int `json:"prompt_tokens,omitempty"`
|
||||
CompletionTokens int `json:"completion_tokens,omitempty"`
|
||||
TotalTokens int `json:"total_tokens,omitempty"`
|
||||
}
|
||||
|
||||
type StructuredLLMClient interface {
|
||||
CompleteStructured(ctx context.Context, req StructuredCompletionRequest, out any) (StructuredCompletionResponse, error)
|
||||
}
|
||||
```
|
||||
|
||||
Define input adapter contract:
|
||||
|
||||
```go
|
||||
type ParseRequest struct {
|
||||
SourceID string `json:"source_id,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Raw []byte `json:"-"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type InputAdapter interface {
|
||||
Key() string
|
||||
Parse(ctx context.Context, req ParseRequest) (*source.SourceDocument, error)
|
||||
}
|
||||
```
|
||||
|
||||
Define extractor contract:
|
||||
|
||||
```go
|
||||
type ExtractionRequest struct {
|
||||
Source *source.SourceDocument `json:"-"`
|
||||
LLMClient StructuredLLMClient `json:"-"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ExtractionResult struct {
|
||||
Candidates []artifacts.Candidate `json:"candidates,omitempty"`
|
||||
Warnings []Warning `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
type Extractor interface {
|
||||
Key() string
|
||||
ArtifactType() string
|
||||
SchemaVersion() string
|
||||
Validators() []Validator
|
||||
Extract(ctx context.Context, req ExtractionRequest) (ExtractionResult, error)
|
||||
}
|
||||
```
|
||||
|
||||
Define validator contract:
|
||||
|
||||
```go
|
||||
type ValidationRequest struct {
|
||||
Source *source.SourceDocument `json:"-"`
|
||||
Candidates []artifacts.Candidate `json:"candidates"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ValidationDecision struct {
|
||||
CandidateIndex int `json:"candidate_index"`
|
||||
Approved bool `json:"approved"`
|
||||
ReasonCode string `json:"reason_code"`
|
||||
Message string `json:"message"`
|
||||
DiagnosticArtifactPath string `json:"diagnostic_artifact_path,omitempty"`
|
||||
}
|
||||
|
||||
type ValidationResult struct {
|
||||
ValidatorName string `json:"validator_name"`
|
||||
Decisions []ValidationDecision `json:"decisions"`
|
||||
Warnings []Warning `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
type Validator interface {
|
||||
Name() string
|
||||
Validate(ctx context.Context, req ValidationRequest) (ValidationResult, error)
|
||||
}
|
||||
```
|
||||
|
||||
Define shared warning type:
|
||||
|
||||
```go
|
||||
type Warning struct {
|
||||
Scope string `json:"scope,omitempty"`
|
||||
ReasonCode string `json:"reason_code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
```
|
||||
|
||||
### Required Tests
|
||||
|
||||
Use compile-time interface assertions with fake implementations:
|
||||
|
||||
```go
|
||||
var _ InputAdapter = fakeAdapter{}
|
||||
var _ Extractor = fakeExtractor{}
|
||||
var _ Validator = fakeValidator{}
|
||||
var _ StructuredLLMClient = fakeLLMClient{}
|
||||
```
|
||||
|
||||
Also test that a fake extractor can return a candidate and expose a validator
|
||||
without importing concrete adapter or extractor packages.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
gofmt -w internal/framework/contracts
|
||||
go test ./internal/framework/contracts
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Stage 5: Cross-Package Contract Composition Tests
|
||||
|
||||
### Goal
|
||||
|
||||
Prove the checkpoint 1 contracts compose at a minimal end-to-end type level
|
||||
without implementing checkpoint 2 runner behavior.
|
||||
|
||||
### Files To Add
|
||||
|
||||
- `internal/framework/contracts/composition_test.go`
|
||||
|
||||
This can live in package `contracts_test` to verify public contracts from the
|
||||
outside.
|
||||
|
||||
### Required Test Scenario
|
||||
|
||||
Create fakes in the test:
|
||||
|
||||
1. fake adapter parses a `ParseRequest` into a valid `SourceDocument`;
|
||||
2. fake extractor receives that source and returns one `artifacts.Candidate`;
|
||||
3. fake validator approves that candidate;
|
||||
4. test manually calls those fakes in sequence.
|
||||
|
||||
Assertions:
|
||||
|
||||
- parsed source passes `source.ValidateDocument`;
|
||||
- candidate source refs pass `source.ValidateRef`;
|
||||
- validator returns one approved decision for the candidate index;
|
||||
- no test type or assertion depends on transcript or D&D concepts.
|
||||
|
||||
Do not add a registry or runner in this stage. Those belong to checkpoint 2.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
gofmt -w internal/framework/contracts
|
||||
go test ./internal/framework/contracts
|
||||
go test ./...
|
||||
go build ./cmd/notarius
|
||||
```
|
||||
|
||||
## Stage 6: Final Checkpoint 1 Review Pass
|
||||
|
||||
### Goal
|
||||
|
||||
Clean up naming, formatting, and accidental scope creep before checkpoint 1 is
|
||||
considered complete.
|
||||
|
||||
### Required Review
|
||||
|
||||
Check:
|
||||
|
||||
- no package names mention transcripts, Seriatim, D&D, spells, NPCs, items, or
|
||||
combat;
|
||||
- no concrete adapter or extractor package exists;
|
||||
- no LLM provider code exists;
|
||||
- no third-party dependency was added;
|
||||
- `README.md` was not updated to describe unimplemented behavior;
|
||||
- roadmap docs remain the only place describing future behavior.
|
||||
|
||||
### Required Validation
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
gofmt -w cmd internal
|
||||
go test ./...
|
||||
go build ./cmd/notarius
|
||||
git status --short
|
||||
```
|
||||
|
||||
The implementation response for this checkpoint should summarize:
|
||||
|
||||
- files added;
|
||||
- tests run;
|
||||
- any deviations from this plan and why.
|
||||
|
||||
## Open Questions
|
||||
|
||||
No blocking open questions remain for checkpoint 1.
|
||||
|
||||
The plan intentionally chooses a narrow generic model and fake-only composition
|
||||
tests. More complete orchestration, registries, real adapters, real extractors,
|
||||
and LLM runtime behavior are deferred to later checkpoint implementation plans.
|
||||
Reference in New Issue
Block a user