397 lines
16 KiB
Markdown
397 lines
16 KiB
Markdown
# Migration Step 4: Public-Facade Adapter Boundary
|
|
|
|
## 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.
|
|
|
|
## Purpose
|
|
|
|
Make Scriptorium's CLI and HTTP adapters genuine consumers of the public
|
|
framework facade before that facade moves to Promptkit. This establishes and
|
|
tests the dependency boundary inside the current repository, where it can be
|
|
changed atomically, before the framework and application are separated across
|
|
repositories.
|
|
|
|
The [accepted split decision](../adr/0002-split-promptkit-from-scriptorium.md)
|
|
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
|
|
|
|
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.
|
|
|
|
## Target State
|
|
|
|
At completion, Scriptorium's executable path is an ordinary consumer of the
|
|
same public framework boundary used by other Go applications:
|
|
|
|
```text
|
|
cmd/scriptorium
|
|
|
|
|
v
|
|
Scriptorium CLI and HTTP adapters
|
|
|
|
|
v
|
|
public Engine, requests, results, errors, and extension interfaces
|
|
|
|
|
v
|
|
framework implementation packages
|
|
```
|
|
|
|
The CLI and HTTP production packages do not import framework-owned internal
|
|
packages. They translate application configuration, flags, and HTTP DTOs into
|
|
public engine configuration and request values; call `Prepare` or `Run`; and
|
|
translate public results and errors back into their owned interfaces.
|
|
|
|
The root facade may continue to use the existing internal implementation during
|
|
Step 4. That implementation moves in later migration steps. The important
|
|
result here is that no Scriptorium-owned adapter or presentation component
|
|
depends on it directly.
|
|
|
|
## Public Facade Requirements
|
|
|
|
### Engine Consumption
|
|
|
|
The current public `Engine`, `Config`, `RunRequest`, `PreparedRun`,
|
|
`RunResult`, request helpers, result values, and existing broad error sentinels
|
|
remain the primary boundary. Step 4 must not introduce a second facade,
|
|
adapter-specific engine, exported internal runner, or public repository
|
|
constructor.
|
|
|
|
Scriptorium-local interfaces may abstract the methods an adapter needs for test
|
|
substitution. They must:
|
|
|
|
- be declared on the consuming side;
|
|
- use only public facade types;
|
|
- contain only `Prepare`, `Run`, or the narrower subset required by that
|
|
consumer; and
|
|
- be satisfied directly by `*Engine`.
|
|
|
|
Promptkit must not acquire CLI, HTTP, status-code, or response-format concepts
|
|
to satisfy these interfaces.
|
|
|
|
### Artifact Reader Extension
|
|
|
|
The public facade will expose the demonstrated artifact-loading extension point:
|
|
|
|
```go
|
|
type ArtifactReader interface {
|
|
Read(context.Context, ArtifactRef) (*Artifact, error)
|
|
}
|
|
|
|
func WithArtifactReader(ArtifactReader) Option
|
|
```
|
|
|
|
This option replaces the engine's ordinary composite artifact reader for all
|
|
input references. The default remains the framework's ordinary inline and
|
|
caller-selected file behavior.
|
|
|
|
The boundary must have the same defensive behavior as other public injection
|
|
points:
|
|
|
|
- a nil reader is rejected as `ErrInvalidConfig`;
|
|
- a reader response of `(nil, nil)` becomes `ErrArtifactLoad` rather than
|
|
causing a panic;
|
|
- reader failures are wrapped as `ErrArtifactLoad` while retaining the
|
|
original error identity for `errors.Is`;
|
|
- conversion between public and internal values does not expose internal domain
|
|
types; and
|
|
- mutable artifact content is copied across the boundary where needed to avoid
|
|
unintended aliasing.
|
|
|
|
This is the only new framework extension interface required by Step 4. No
|
|
public prompt repository, profile repository, renderer, validator, or internal
|
|
runner interface is needed for the current adapters.
|
|
|
|
### Public Error Detail
|
|
|
|
The facade's existing broad errors remain authoritative for general consumers.
|
|
To preserve Scriptorium's current HTTP contract without inspecting Promptkit
|
|
internals, the public error chain must additionally retain stable identities
|
|
for:
|
|
|
|
- profile selection being required because neither the request nor the prompt
|
|
supplies a profile; and
|
|
- a selected credential environment variable being unset or empty.
|
|
|
|
These identities will be exposed as `ErrProfileRequired` and
|
|
`ErrAPIKeyEnvMissing`. Each remains nested under `ErrInvalidRequest`, so callers
|
|
that need only the broad category continue to match it. Scriptorium uses the
|
|
more specific identities only to preserve its existing HTTP error codes.
|
|
|
|
No error-string parsing is permitted. Prompt, profile, artifact, render,
|
|
generation, and validation failures continue to use the existing public
|
|
sentinels. Scriptorium-owned HTTP artifact-policy errors remain in Scriptorium
|
|
and survive wrapping through the public artifact-reader boundary.
|
|
|
|
## CLI Boundary
|
|
|
|
The `run` and `render` commands will construct the public engine from resolved
|
|
application settings:
|
|
|
|
- `prompt_dir`, `profile_dir`, and `schema_dir` map to public engine
|
|
configuration;
|
|
- the public engine supplies the built-in profile registry, renderer,
|
|
validator, ordinary artifact reader, and default OpenAI-compatible client;
|
|
- CLI input paths map to public file artifact references;
|
|
- CLI runtime flags map to the public request override while preserving
|
|
numeric presence, including explicit zero; and
|
|
- omitted application values remain omitted so framework defaults are not
|
|
duplicated in Scriptorium.
|
|
|
|
`run` consumes the public result for artifact output, validation exit status,
|
|
and the stderr summary. `render` consumes the public prepared value. The
|
|
Scriptorium-owned prepared-run formatter will accept public prepared values
|
|
instead of internal domain values without changing its text or JSON contract.
|
|
|
|
The `serve` command will construct the same public engine with Scriptorium's
|
|
restricted artifact reader injected through `WithArtifactReader`. It will pass
|
|
that engine to the HTTP handler through a Scriptorium-local run interface.
|
|
|
|
CLI construction must handle public engine-construction errors explicitly.
|
|
The executable must not construct an internal LLM client, repository, renderer,
|
|
validator, artifact reader, or runner.
|
|
|
|
## HTTP Boundary
|
|
|
|
The HTTP handler's consumer interface will accept public `RunRequest` values
|
|
and return public `RunResult` values. DTO mapping will preserve the existing
|
|
HTTP contract:
|
|
|
|
- strict JSON decoding and request-size enforcement remain in Scriptorium;
|
|
- prompt, profile, input, variable, and execution-override fields map to their
|
|
corresponding public values;
|
|
- pointer-valued numeric overrides retain omitted-versus-explicit-zero
|
|
semantics;
|
|
- raw API keys remain absent from the HTTP request shape;
|
|
- public run results map to the current response DTOs;
|
|
- raw model output remains opt-in; and
|
|
- response-size enforcement remains an HTTP concern.
|
|
|
|
HTTP error mapping will inspect only:
|
|
|
|
- public framework errors;
|
|
- Scriptorium's HTTP artifact-policy errors; and
|
|
- standard-library transport errors owned by the handler.
|
|
|
|
It will not inspect internal prompt, profile, artifact, domain, or use-case
|
|
errors. Existing HTTP statuses, error codes, and sanitized messages remain
|
|
unchanged.
|
|
|
|
## HTTP Artifact Policy
|
|
|
|
The restricted artifact reader remains Scriptorium-owned and becomes an
|
|
implementation of the public `ArtifactReader` interface. It belongs with the
|
|
HTTP adapter rather than the Promptkit-destined general artifact package.
|
|
|
|
The reader will continue to:
|
|
|
|
- resolve inline references without permitting empty inline bodies;
|
|
- deny file references when no artifact root is configured;
|
|
- enforce the configured maximum artifact size;
|
|
- enforce the documented lexical root-containment rule without resolving
|
|
symlinks;
|
|
- return complete public artifact metadata; and
|
|
- preserve distinct Scriptorium errors for a denied or out-of-root file and an
|
|
oversized file.
|
|
|
|
The public engine treats those errors as artifact-load failures while preserving
|
|
their identities. The HTTP mapper checks the Scriptorium-specific identity
|
|
before the broad public `ErrArtifactLoad` identity, retaining the current
|
|
`artifact_not_allowed`, `artifact_too_large`, and general artifact-read
|
|
outcomes.
|
|
|
|
General inline and unrestricted file reading remains framework-owned. Step 4
|
|
separates the HTTP policy from that implementation far enough that each side
|
|
can later move to its target repository without redesigning the interface.
|
|
|
|
## Package And Dependency Boundaries
|
|
|
|
The completed dependency state is:
|
|
|
|
| Component | Permitted framework dependency |
|
|
| --- | --- |
|
|
| `internal/adapter/cli` | Public facade types, constructors, options, errors, and methods only |
|
|
| `internal/adapter/http` | Public facade types and errors only; Scriptorium-owned HTTP artifact policy remains local |
|
|
| `internal/format` | Public prepared-run and rendered-message values only |
|
|
| `internal/config` | Scriptorium application settings and Scriptorium-owned defaults; no framework orchestration |
|
|
| `cmd/scriptorium` | CLI adapter only |
|
|
| Root facade | Existing internal framework implementation until extraction |
|
|
|
|
In particular, Scriptorium-owned adapter, formatter, and HTTP artifact-policy
|
|
production files must not import:
|
|
|
|
- `internal/domain`;
|
|
- `internal/usecase`;
|
|
- `internal/promptdef` or `internal/prompt`;
|
|
- `internal/profile` or `internal/profile/builtin`;
|
|
- `internal/validate`;
|
|
- `internal/llm`; or
|
|
- the Promptkit-destined general artifact implementation.
|
|
|
|
Tests for Scriptorium-owned components should follow the same public boundary
|
|
except when directly testing a Scriptorium-owned package.
|
|
|
|
## Observable Behavior
|
|
|
|
Step 4 is an architectural refactor plus the minimum additive public extension
|
|
surface required to support it. It is not a redesign of the executable
|
|
interfaces.
|
|
|
|
The following behavior must remain unchanged:
|
|
|
|
- CLI commands, flags, aliases, precedence, output routing, summaries, and exit
|
|
codes;
|
|
- application configuration discovery, strict decoding, fields, and defaults;
|
|
- HTTP routes, strict decoding, DTOs, statuses, codes, messages, and limits;
|
|
- HTTP artifact containment and size enforcement;
|
|
- prompt/profile selection and override precedence;
|
|
- explicit numeric-zero behavior;
|
|
- built-in profile fallback and custom-profile overlays;
|
|
- structured-output and validation behavior;
|
|
- timeout layering;
|
|
- secret handling and redaction; and
|
|
- maintained executable examples.
|
|
|
|
The intended public additions are limited to artifact-reader injection and the
|
|
specific error identities required by the HTTP mapper. No compatibility shim is
|
|
needed because this repository still owns the facade during Step 4 and the
|
|
overall migration is intentionally breaking.
|
|
|
|
## Test Ownership And Verification
|
|
|
|
Tests will protect the boundary at the layer that owns each risk:
|
|
|
|
- public engine tests own artifact-reader option validation, conversion,
|
|
invocation, nil-response handling, error wrapping, and error identity;
|
|
- CLI tests own flag and configuration mapping into public requests, public
|
|
engine wiring, presentation, output, and exit behavior;
|
|
- HTTP tests own strict DTO mapping to public requests, public result mapping,
|
|
error/status mapping, limits, and raw-output opt-in;
|
|
- HTTP artifact-reader tests own denied, contained, escaped, oversized, inline,
|
|
cancellation, and metadata behavior;
|
|
- formatter tests own stable text and JSON presentation of public prepared
|
|
values; and
|
|
- existing framework tests continue to own orchestration, source, validation,
|
|
provider, and broad public error behavior.
|
|
|
|
Adapter tests that currently construct internal runners or assert internal
|
|
sentinels will be rewritten against the public engine or small public-typed
|
|
fakes. Duplicate framework-semantic assertions should be removed when the
|
|
public contract suite already owns the risk.
|
|
|
|
The final suite must include an enforceable dependency check showing that
|
|
Scriptorium-owned adapter and presentation production packages do not import
|
|
Promptkit-destined internal packages. This may be a focused architecture test
|
|
or an equivalent deterministic repository check; it must diagnose the
|
|
forbidden import clearly.
|
|
|
|
## Documentation Outcome
|
|
|
|
When the boundary is implemented, current-behavior documentation will be
|
|
reconciled in the same change:
|
|
|
|
- the public Go package contract will define `ArtifactReader`,
|
|
`WithArtifactReader`, and the new error identities;
|
|
- adapter internals will describe public-engine composition and public-value
|
|
mapping;
|
|
- source internals will distinguish the public reader extension, general
|
|
framework readers, and Scriptorium's HTTP reader;
|
|
- the internal overview and architecture policy will reflect that executable
|
|
adapters consume the public facade; and
|
|
- CLI, configuration, HTTP, integration, and operations contracts will change
|
|
only if verification finds an observable correction is necessary.
|
|
|
|
Documents will keep exact external contracts in their existing canonical
|
|
owners and link rather than duplicate them.
|
|
|
|
## Required Validation Outcome
|
|
|
|
The completed boundary must pass:
|
|
|
|
- the full Go test suite;
|
|
- `go vet` across all packages;
|
|
- a temporary-output executable build;
|
|
- repeated public artifact-reader and adapter boundary tests;
|
|
- both maintained application configurations;
|
|
- maintained render, HTTP-request, and Go-package examples;
|
|
- CLI and HTTP smoke checks that exercise the public engine path;
|
|
- the forbidden-import dependency check;
|
|
- local Markdown-link validation; and
|
|
- whitespace validation.
|
|
|
|
All default validation remains offline, deterministic, and independent of real
|
|
credentials.
|
|
|
|
## Out Of Scope
|
|
|
|
Step 4 does not:
|
|
|
|
- create the Promptkit repository or module;
|
|
- change the module or root package name;
|
|
- move framework implementation packages or built-in profiles out of this
|
|
repository;
|
|
- remove the current root facade;
|
|
- add compatibility aliases or forwarding packages;
|
|
- make Scriptorium depend on an external Promptkit revision;
|
|
- broadly export framework repositories, domain values, validators, renderers,
|
|
or runner constructors;
|
|
- redesign prompt, profile, schema, request, response, CLI, or configuration
|
|
formats;
|
|
- change HTTP containment from lexical path checking to symlink resolution;
|
|
- add new execution or repair behavior; or
|
|
- perform unrelated facade cleanup.
|
|
|
|
Those changes belong to later migration steps or a separately accepted feature.
|
|
|
|
## Completion Criteria
|
|
|
|
Step 4 is complete when:
|
|
|
|
- CLI `run` and `render` execute through the public engine;
|
|
- CLI `serve` injects the Scriptorium-owned restricted reader into the public
|
|
engine and passes that engine to the HTTP handler;
|
|
- HTTP and CLI map only public framework request, result, and error values;
|
|
- prepared-run formatting consumes the public prepared value;
|
|
- the public artifact-reader extension has the specified validation,
|
|
conversion, nil-response, and error-preservation behavior;
|
|
- public error identities preserve every distinction required by the current
|
|
HTTP contract;
|
|
- Scriptorium-owned adapters, formatter, and HTTP artifact reader have no
|
|
Promptkit-destined internal imports;
|
|
- HTTP containment, limits, error mapping, and all existing executable
|
|
behavior remain protected by passing tests;
|
|
- current-behavior documentation reflects the implemented boundary;
|
|
- every required validation check passes;
|
|
- no out-of-scope extraction or compatibility work is included; and
|
|
- the main migration roadmap records Step 4 as complete and identifies
|
|
repository creation in Step 5 as the next gate.
|
|
|
|
Migration Step 5 must not begin until these criteria are satisfied.
|
|
|
|
## Lifecycle
|
|
|
|
This feature roadmap is a temporary migration artifact. It may be removed after
|
|
Step 4 is complete and no longer needs to guide active work; repository history
|
|
will retain the decision and completion record.
|