Add an initial staged implementation prompt fro the core contracts and skeleton

This commit is contained in:
2026-07-03 00:58:40 -05:00
parent 9fd63d0422
commit 60682e977e
2 changed files with 550 additions and 69 deletions

View File

@@ -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

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