# 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.