From 60682e977ec4490bdcb3379989a0c447b11d9069 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Fri, 3 Jul 2026 00:58:40 -0500 Subject: [PATCH] Add an initial staged implementation prompt fro the core contracts and skeleton --- docs/roadmap/1-core-contracts-and-skeleton.md | 85 +-- docs/roadmap/implementation.md | 534 ++++++++++++++++++ 2 files changed, 550 insertions(+), 69 deletions(-) create mode 100644 docs/roadmap/implementation.md diff --git a/docs/roadmap/1-core-contracts-and-skeleton.md b/docs/roadmap/1-core-contracts-and-skeleton.md index 3c6deb9..b7430fd 100644 --- a/docs/roadmap/1-core-contracts-and-skeleton.md +++ b/docs/roadmap/1-core-contracts-and-skeleton.md @@ -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 diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..6fa5c7e --- /dev/null +++ b/docs/roadmap/implementation.md @@ -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 ""` 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.