diff --git a/docs/consumers/pkg-scriptorium.md b/docs/consumers/pkg-scriptorium.md index eb6da72..ef71f30 100644 --- a/docs/consumers/pkg-scriptorium.md +++ b/docs/consumers/pkg-scriptorium.md @@ -113,11 +113,16 @@ counts. `RenderedPrompt`, `RenderedMessage`, `CacheControl`, and `ArtifactReader` implements `Read(context.Context, ArtifactRef) (*Artifact, error)`. Supplying it through -`WithArtifactReader` replaces the engine's default inline and file reader for -all inputs. Reader errors remain available through `errors.Is` alongside -`ErrArtifactLoad`; a nil artifact with no error is treated as an artifact-load -failure. Readers must supply artifact metadata and should not retain or mutate -the caller's values. +`WithArtifactReader` replaces, rather than extends, the engine's default inline +and file reader for every input. Omitting the option retains that default; +`WithArtifactReader(nil)` makes engine construction fail with +`ErrInvalidConfig`. + +Reader failures are surfaced as errors matching `ErrArtifactLoad` while +preserving the reader's original error identity for `errors.Is`. A `(nil, nil)` +reader response is also an artifact-load failure. Readers are responsible for +artifact metadata, although the engine assigns the input-map name when the +returned name is empty; readers should not retain or mutate caller values. ## Requests, Inputs, And Overrides @@ -191,5 +196,8 @@ Public methods preserve these sentinel checks through `errors.Is`: - `ErrLLMGenerate` - `ErrValidation` -For interface selection and operational responsibilities, see the -[consumer integration overview](api.md). +`ErrProfileRequired` and `ErrAPIKeyEnvMissing` each also match +`ErrInvalidRequest`, so callers can select either the broad request category or +the specific condition. + +For the HTTP interface, see the [HTTP API reference](../api.md). diff --git a/docs/internal/adapters.md b/docs/internal/adapters.md index b37e1b0..bbdbcb0 100644 --- a/docs/internal/adapters.md +++ b/docs/internal/adapters.md @@ -2,9 +2,9 @@ ## Purpose -Adapters translate external inputs into domain requests, compose dependencies, -and translate domain results or errors back to their interface. They own IO and -presentation mechanics; use-case decisions remain in `internal/usecase`. +Adapters translate external inputs into public engine requests and translate +public results or errors back to their interface. They own IO and presentation +mechanics; use-case decisions remain behind the root `scriptorium` facade. External contracts are canonical in the [CLI reference](../cli.md), [HTTP API reference](../api.md), and [Go package contract](../consumers/pkg-scriptorium.md). @@ -14,35 +14,37 @@ reference](../api.md), and [Go package contract](../consumers/pkg-scriptorium.md - `cmd/scriptorium` passes process arguments and streams to `internal/adapter/cli`. - `internal/adapter/cli` parses commands, resolves application settings through - `internal/config`, constructs a runner, and owns process output handling. -- `internal/adapter/http` decodes DTOs, maps them to `domain.RunRequest`, calls - a runner interface, and maps errors and results to HTTP DTOs. + `internal/config`, constructs the public engine, and owns process output + handling. +- `internal/adapter/http` decodes DTOs, maps them to public run requests, + calls its local public `Runner` interface, and maps public errors and results + to HTTP DTOs. - The root `scriptorium` package maps its public types and options to internal collaborators and maps selected internal errors to public sentinels. -- `internal/format` formats prepared runs for the CLI; `internal/llm`, - `internal/prompt`, and source packages supply runner dependencies. +- `internal/format` formats public prepared runs for the CLI. ## Wiring Flows ### CLI -The CLI resolves configuration before constructing dependencies. `run` builds a -runner with the ordinary composite artifact reader and invokes `Runner.Run`; -`render` uses the same wiring and invokes `Runner.Prepare`; `serve` replaces the -file reader with the restricted artifact reader, builds an HTTP handler, and -starts the server. +The CLI resolves configuration before constructing the public engine. `run` +calls `Engine.Run` with a public request and `render` calls `Engine.Prepare` +with the same request mapping. `serve` constructs the HTTP-owned restricted +artifact reader, injects it with `WithArtifactReader`, passes the resulting +engine directly to the HTTP handler, and starts the server. Parser state records whether numeric runtime values were explicitly supplied. -That presence is carried into `domain.ExecutionTargetOverride`, allowing the -runner to distinguish omitted values from explicit zero overrides. +That presence is carried into `scriptorium.ExecutionTargetOverride`, allowing +the engine to distinguish omitted values from explicit zero overrides. ### HTTP The handler first enforces transport limits, strict JSON decoding, and the -minimal request shape. It maps DTO values to domain types without deciding +minimal request shape. It maps DTO values to public types without deciding prompt selection, source behavior, or validation semantics. On success it maps -the domain result to the response DTO; on failure it uses `errors.Is` over -runner, source, artifact, and profile errors to choose the public error mapping. +the public result to the response DTO; on failure it uses `errors.Is` over +public framework errors and HTTP-local artifact-policy errors to choose the +public error mapping. The [HTTP API reference](../api.md) owns the route, DTO schema, status codes, and externally observable limit behavior. @@ -57,10 +59,10 @@ set and keeps direct request API keys out of public results. ## Package-Local Guarantees -- Adapters do not embed runner orchestration or source-loading decisions. +- Adapters do not embed framework orchestration or source-loading decisions. - Configuration is resolved before adapter dependency composition. -- CLI and HTTP create runners without a repairer; a repairer is available only - through explicit internal runner construction. +- CLI and HTTP consume the public engine without a repairer; a repairer remains + available only through explicit internal runner construction. - DTO conversion preserves explicit numeric-override presence. - Error mapping matches error identities, not error text. - No adapter creates durable run state; caller-selected output files are not @@ -105,9 +107,10 @@ sufficiency guidance. ### Adapter Capabilities -1. Define or reuse the appropriate domain or use-case interface boundary. -2. Implement translation and IO behavior without moving use-case decisions out - of `internal/usecase`. +1. Define or reuse an adapter-local consumer interface with public facade + types when a test seam is needed. +2. Implement translation and IO behavior without moving framework decisions out + of the public engine. 3. Add focused mapping, parsing, and error-behavior tests. 4. Update this document and the affected public or integration contract. Update [source internals](sources.md) when source-loading behavior changes. diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 8721015..2b5b72d 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -17,8 +17,8 @@ and invariants; public behavior belongs in the linked contracts. | Component | Implemented responsibility | References | | --- | --- | --- | -| `internal/adapter/cli` | Parses CLI commands, applies application wiring, and handles process input and output. | [CLI contract](../cli.md), [adapter internals](adapters.md) | -| `internal/adapter/http` | Maps HTTP requests and responses to domain operations and maps public errors. | [HTTP API contract](../api.md), [adapter internals](adapters.md) | +| `internal/adapter/cli` | Parses CLI commands, constructs the public engine from application settings, and handles process input and output. | [CLI contract](../cli.md), [adapter internals](adapters.md) | +| `internal/adapter/http` | Maps HTTP requests and responses through public engine values, maps public errors, and owns restricted HTTP artifact policy. | [HTTP API contract](../api.md), [adapter internals](adapters.md) | | `internal/domain` | Defines core request, result, output-contract, and LLM-boundary types. | [runner internals](runner.md) | | `internal/usecase` | Implements `Runner` preparation, execution, validation coordination, and the repairer boundary. | [runner internals](runner.md) | @@ -32,14 +32,14 @@ and invariants; public behavior belongs in the linked contracts. | `internal/profile` | Loads filesystem and `fs.FS` execution profiles and combines profile repositories. | [configuration contract](../config.md), [source internals](sources.md) | | `internal/profile/builtin` | Provides embedded built-in execution profiles as a repository. | [configuration contract](../config.md), [source internals](sources.md) | | `internal/filecatalog` | Provides shared YAML discovery and source-root helpers. | [source internals](sources.md) | -| `internal/artifact` | Reads inline and file-backed input artifacts. | [configuration contract](../config.md), [HTTP API contract](../api.md), [source internals](sources.md) | +| `internal/artifact` | Provides the framework's ordinary inline and unrestricted file artifact reader. | [configuration contract](../config.md), [source internals](sources.md) | | `internal/prompt` | Renders prompt templates into messages. | [runner internals](runner.md) | ## Formatting, Validation, And Model Access | Component | Implemented responsibility | References | | --- | --- | --- | -| `internal/format` | Formats prepared-run information for CLI output. | [CLI contract](../cli.md), [adapter internals](adapters.md) | +| `internal/format` | Formats public prepared-run information for CLI output. | [CLI contract](../cli.md), [adapter internals](adapters.md) | | `internal/validate` | Defines validation interfaces and provides standard filesystem and `fs.FS` schema validation. | [configuration contract](../config.md), [source internals](sources.md), [runner internals](runner.md) | | `internal/llm` | Defines the provider-neutral LLM client boundary and its OpenAI-compatible implementation. | [OpenAI-compatible integration](../integrations/openai-compatible-chat.md), [LLM internals](llm.md), [runner internals](runner.md) | diff --git a/docs/internal/runner.md b/docs/internal/runner.md index 3582f20..b4e1378 100644 --- a/docs/internal/runner.md +++ b/docs/internal/runner.md @@ -25,8 +25,9 @@ contracts. - an optional `OutputRepairer`. `NewRunner` constructs a runner without a repairer. `NewRunnerWithRepairer` -accepts one explicitly. Adapters and the public engine choose concrete -repositories and readers; the runner does not load application configuration. +accepts one explicitly. The public engine chooses concrete repositories and +readers; executable adapters reach the runner only through that engine. The +runner does not load application configuration. ## Prepare Flow @@ -68,8 +69,9 @@ validation mode is JSON or JSON Schema. Each repair receives the previous output, validation errors, effective target, structured-output specification, and attempt metadata; every repaired result is validated again. -`NewDefaultOutputRepairer` delegates to the injected LLM client. CLI, HTTP, and -the public engine use `NewRunner` and therefore do not inject this repairer. +`NewDefaultOutputRepairer` delegates to the injected LLM client. The public +engine, and therefore CLI and HTTP, uses `NewRunner` and does not inject this +repairer. ## Error Translation @@ -104,7 +106,6 @@ values in prepared or run results. Inspect: - `internal/usecase/runner_test.go` -- `internal/usecase/integration_test.go` - `engine_test.go` When changing orchestration: diff --git a/docs/internal/sources.md b/docs/internal/sources.md index 37c5031..51b08ea 100644 --- a/docs/internal/sources.md +++ b/docs/internal/sources.md @@ -50,14 +50,11 @@ failures are operational errors. ## Artifacts -`internal/artifact` composes inline and file readers. The ordinary composite -reader used by CLI and the public engine reads file references from the process -filesystem. `internal/adapter/http` provides the restricted public artifact -reader for HTTP containment: it combines inline reading with a rooted file -reader and optional byte limit. The existing internal restricted composite -reader remains a temporary bridge for the current handler and serve wiring; it -does not define the HTTP reader's long-term boundary or carry a compatibility -promise. +`internal/artifact` owns the framework's ordinary inline and unrestricted file +reader. The public engine uses it by default and permits consumers to replace it +for every input through the public `ArtifactReader` extension. The +HTTP adapter owns its restricted reader for HTTP containment: `serve` injects +that reader into the public engine with `WithArtifactReader`. The rooted reader cleans paths and applies lexical containment without resolving symlinks. It checks relative references against the configured root and accepts @@ -70,8 +67,9 @@ by the [HTTP API reference](../api.md); deployment permissions belong in Source packages report repository, decoding, duplicate, validation, and read failures to their callers. They do not select public status codes or response -schemas. The runner wraps source failures with use-case categories; adapters map -them to their own external contract. +schemas. The runner categorizes source failures and the public engine preserves +the corresponding public error identities; adapters map those identities to +their own external contract. Source reads use current filesystem or `fs.FS` content for each request. These packages create no manifests, checkpoints, or durable run state. @@ -86,7 +84,6 @@ Inspect: - `internal/artifact/reader_test.go` - `internal/adapter/http/artifact_reader_test.go` - `internal/validate/standard_validator_test.go` -- `internal/usecase/integration_test.go` - `engine_test.go` When updating prompt, profile, schema, or built-in assets: diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index 5af6e58..a1bb835 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -8,15 +8,18 @@ It is for developers and LLM coding agents. User-facing behavior belongs in `REA Scriptorium is a narrow prompt-execution application with three executable entry paths: CLI `run`, CLI `render`, and the HTTP service started by `serve`. -It also provides a public Go package for in-process use. Its current component -inventory is maintained in the [internal overview](../internal/overview.md). +It also provides a public Go package for in-process use. Executable adapters +consume framework behavior through that public facade; the facade continues to +compose the framework implementation inside this single repository. Its current +component inventory is maintained in the [internal overview](../internal/overview.md). Domain behavior is centralized in `internal/usecase` and `internal/domain`. ## Core Principles - Keep orchestration narrow: Scriptorium executes one prompt request; it is not a multi-step workflow engine. -- Keep adapter logic thin: adapters map external shapes to domain requests/results and should not hold domain decisions. +- Keep adapter logic thin: adapters map external shapes to public engine + requests/results and should not hold framework decisions. - Keep boundaries explicit: repositories/loaders/renderers/validators/LLM client stay behind package interfaces. - Keep external decoding strict: configuration, prompt, and profile YAML and HTTP JSON should reject unknown fields. @@ -26,6 +29,9 @@ Domain behavior is centralized in `internal/usecase` and `internal/domain`. - Adapters translate external shapes and IO concerns; they do not make use-case decisions. +- Executable adapters and prepared-run formatting use the public facade for + framework behavior rather than importing framework implementation packages + directly. - Use-case and domain code depend on explicit repository, renderer, validator, and LLM interfaces rather than adapter implementations. - Source, rendering, validation, and LLM implementations remain behind their @@ -52,7 +58,8 @@ their implementation. ## Error Handling And Logging - Wrap errors with domain/operation context. -- Map domain errors to adapter-appropriate statuses/codes without leaking sensitive internals. +- Map public error identities to adapter-appropriate statuses/codes without + leaking sensitive internals. - Never emit raw secret values. ## Testing And Documentation diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 12beb3e..614a48e 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -2,7 +2,9 @@ ## Status -Proposed. Implement each stage in order and satisfy its gate before continuing. +Complete as of 2026-07-28. All implementation gates, the dependency guard, +documentation reconciliation, and required validation passed. Migration Step 5 +repository creation is the next gate; it was not started here. ## Objective diff --git a/docs/roadmap/migration.md b/docs/roadmap/migration.md index 98c2548..d4904a3 100644 --- a/docs/roadmap/migration.md +++ b/docs/roadmap/migration.md @@ -2,7 +2,7 @@ ## Status -Accepted plan. Steps 1 through 3 are complete. Steps 4 through 9 remain +Accepted plan. Steps 1 through 4 are complete. Steps 5 through 9 remain proposed and are not yet implemented. ## Objective @@ -125,8 +125,8 @@ is accepted and records the required ownership and coordination decisions. Strengthen or add contract-focused tests where needed so extraction can be verified without relying on package placement. -The accepted implementation scope and intended completion state are in the -[Step 3 framework-characterization roadmap](step3.md). +The completed Step 3 gate records the accepted implementation scope and +intended completion state. Preserve coverage of: @@ -172,6 +172,12 @@ implementations. **Gate:** The CLI and HTTP adapters use only the public framework API for framework behavior, and all tests and documented smoke commands pass. +**Gate status:** Complete as of 2026-07-28. CLI `run`, `render`, and `serve`, +the HTTP handler, and prepared-run formatting use the public facade; the +restricted HTTP reader is injected through the public extension point. The +dependency guard, full tests, vet, build, race checks, maintained examples, and +configuration smoke checks passed. Step 5 repository creation is next. + ### Step 5: Create The Promptkit Repository Create the Promptkit repository and Go module as an explicit out-of-band diff --git a/docs/roadmap/step4.md b/docs/roadmap/step4.md index 1204ab2..5f8e81e 100644 --- a/docs/roadmap/step4.md +++ b/docs/roadmap/step4.md @@ -2,9 +2,10 @@ ## Status -Proposed. Migration Steps 1 through 3 are complete; this roadmap defines the -required target state for Step 4. The separate -[implementation plan](implementation.md) defines the ordered work. +Complete as of 2026-07-28. The public-facade adapter boundary is established; +the [implementation plan](implementation.md) records the completed work, and +the [main migration roadmap](migration.md) identifies repository creation as +the next gate. ## Purpose @@ -19,31 +20,18 @@ owns the long-term project boundary. The [main migration roadmap](migration.md) owns the overall sequence. This feature roadmap defines the desired Step 4 state, not an implementation sequence. -## Current Gap +## Achieved Boundary -The public `Engine` facade already supports the ordinary `Prepare` and `Run` -workflows, directory and alternate framework sources, injected model clients, -public result values, and broad public error classification. The executable -adapters do not yet use that boundary consistently: - -- the CLI constructs framework repositories, readers, renderer, validator, and - OpenAI-compatible client directly, then calls the internal runner; -- the HTTP handler accepts internal domain request and result values; -- HTTP error mapping inspects framework-internal sentinels; -- the HTTP artifact-containment reader implements the internal artifact-reader - interface; -- prepared-run formatting accepts an internal domain value; and -- adapter tests frequently construct internal runners or use internal domain - values. - -Those dependencies would prevent Scriptorium from compiling after the -framework packages move to Promptkit. They also allow the executable to exercise -a different composition path from downstream Go consumers. +The CLI, HTTP handler, and prepared-run formatter now consume public engine +values and errors. `serve` injects Scriptorium's HTTP-owned restricted artifact +reader through the public extension point, while the root facade continues to +compose the framework implementation inside this repository. A repository-level +dependency test protects the direct-import boundary. ## Target State -At completion, Scriptorium's executable path is an ordinary consumer of the -same public framework boundary used by other Go applications: +Scriptorium's executable path is an ordinary consumer of the same public +framework boundary used by other Go applications: ```text cmd/scriptorium diff --git a/internal/adapter/dependency_test.go b/internal/adapter/dependency_test.go new file mode 100644 index 0000000..5dbc7dc --- /dev/null +++ b/internal/adapter/dependency_test.go @@ -0,0 +1,64 @@ +package adapter_test + +import ( + "go/parser" + "go/token" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "testing" +) + +func TestScriptoriumAdaptersUseOnlyPublicFrameworkBoundary(t *testing.T) { + _, testFile, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("locate dependency guard source") + } + + adapterDir := filepath.Dir(testFile) + directories := []string{ + filepath.Join(adapterDir, "cli"), + filepath.Join(adapterDir, "http"), + filepath.Join(adapterDir, "..", "format"), + } + forbidden := map[string]struct{}{ + "gitea.maximumdirect.net/eric/scriptorium/internal/domain": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/usecase": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/promptdef": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/prompt": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/profile": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/profile/builtin": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/validate": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/llm": {}, + "gitea.maximumdirect.net/eric/scriptorium/internal/artifact": {}, + } + + for _, directory := range directories { + entries, err := os.ReadDir(directory) + if err != nil { + t.Fatalf("read source directory %s: %v", directory, err) + } + for _, entry := range entries { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") { + continue + } + + path := filepath.Join(directory, entry.Name()) + file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly) + if err != nil { + t.Fatalf("parse imports in %s: %v", path, err) + } + for _, imported := range file.Imports { + importPath, err := strconv.Unquote(imported.Path.Value) + if err != nil { + t.Fatalf("parse import path in %s: %v", path, err) + } + if _, found := forbidden[importPath]; found { + t.Errorf("%s directly imports forbidden framework package %s", path, importPath) + } + } + } + } +}