Refocus internal component documentation

This commit is contained in:
2026-07-26 14:20:06 +00:00
parent c927b7819d
commit ff31f8daf8
6 changed files with 297 additions and 384 deletions

View File

@@ -2,148 +2,86 @@
## Purpose
Adapters translate external interfaces into domain requests and translate domain results back out. They wire dependencies, apply app config, and own IO concerns, but they do not make runner decisions.
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`.
Source-loading behavior belongs in `docs/internal/sources.md`. User-facing CLI, HTTP, and package contracts belong in `docs/cli.md`, `docs/api.md`, and `docs/consumers/pkg-scriptorium.md`.
External contracts are canonical in the [CLI reference](../cli.md), [HTTP API
reference](../api.md), and [Go package contract](../consumers/pkg-scriptorium.md).
## Adapter Map
## Components And Collaborators
- `cmd/scriptorium`: process entrypoint.
- `internal/adapter/cli`: command parsing, config handoff, runner construction, stdout/stderr, exit codes.
- `internal/adapter/http`: `POST /v1/runs` request/response mapping and HTTP error/status mapping.
- root package `scriptorium`: public Go facade over internal runner types and dependencies.
- `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.
- 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.
Supporting implementation packages used during adapter wiring:
## Wiring Flows
- `internal/config`
- `internal/defaults`
- `internal/format`
- `internal/llm`
- `internal/prompt`
### CLI
## Inputs And Outputs
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.
CLI adapter:
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.
- Input: process args, optional config file, filesystem sources, environment variables.
- Output: process exit code, stdout artifact/prepared output, stderr summaries and errors.
### HTTP
HTTP adapter:
The handler first enforces transport limits, strict JSON decoding, and the
minimal request shape. It maps DTO values to domain 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.
- Input: HTTP request method/path/headers/body for `POST /v1/runs`.
- Output: JSON success or error body with mapped status code.
The [HTTP API reference](../api.md) owns the route, DTO schema, status codes,
and externally observable limit behavior.
Public Go facade:
### Public Go Facade
- Input: typed `scriptorium.Config`, `Option`, and `RunRequest` values.
- Output: typed `PreparedRun` and `RunResult` values plus public sentinel errors.
`NewEngine` applies public options, selects filesystem, `fs.FS`, single-file,
or in-memory dependencies, and constructs a runner. The conversion functions
copy maps and slices across the boundary so callers do not receive internal
domain values. The facade maps selected internal errors to the public sentinel
set and keeps direct request API keys out of public results.
## Boundaries
## Package-Local Guarantees
- Adapters convert external shapes to `domain.RunRequest` and back.
- Runner orchestration remains in `internal/usecase`.
- Prompt/profile/schema/artifact source rules remain in repository, validator, and artifact packages.
- LLM provider request serialization remains in `internal/llm`.
- Public package types are facade types; internal domain types do not leak across the package boundary.
- Adapters do not embed runner 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.
- 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
application state.
## Config Fields Used
## Failure And Verification Boundaries
Adapter app settings:
Keep external error payloads concise, preserve strict external decoding, and do
not serialize resolved secret values. Validation content failures remain result
state; runtime failures remain errors for the relevant adapter to map.
- `prompt_dir`
- `profile_dir`
- `schema_dir`
- `server.addr`
- `server.artifact_root`
- `server.max_request_bytes`
- `server.max_artifact_bytes`
- `server.max_response_bytes`
- `defaults.render_format`
Execution request/profile settings passed through the runner:
- `endpoint`
- `model`
- `temperature`
- `max_tokens`
- `top_p`
- `timeout_seconds`
- `service_tier`
- `api_key_env`
- `reasoning_effort`
- `extra_params`
CLI and HTTP preserve numeric override presence so omitted values and explicit zero values remain distinct.
## CLI Adapter
Implemented commands:
- `run`
- `render`
- `serve`
Behavior:
- `run` constructs a runner with direct filesystem artifact reading and calls `Runner.Run`.
- `render` constructs a runner and calls `Runner.Prepare`; it does not call the LLM.
- `serve` constructs a restricted artifact reader and HTTP handler, then starts an unauthenticated HTTP server.
- `run` exits `2` when generation succeeds but validation fails.
- parse, runtime, and output-write errors exit `1`.
- deprecated `--prompt-id` and `--profile-id` aliases are accepted.
## HTTP Adapter
Behavior:
- Accepts only `POST /v1/runs`.
- Decodes JSON strictly and rejects unknown fields and trailing JSON tokens.
- Rejects empty `prompt_id` and empty `inputs` before calling the runner.
- Does not accept raw API key values in the request body.
- Returns validation failures as `200` responses with failed validation details.
- Maps request-body, artifact, and encoded-response size failures to `413`.
- Maps domain and repository errors to stable error codes without returning wrapped internal cause text.
The HTTP adapter has no built-in authentication or authorization. Deployment controls must be provided outside the process.
## Public Go Facade
Behavior:
- `NewEngine` wires the same default runner components as CLI/HTTP unless options override them.
- Prompt, profile, and schema sources may come from directories, single files, or `fs.FS` roots.
- `WithProfiles` adds in-memory profiles ahead of file-backed and built-in profiles.
- `WithLLMClient` injects custom model behavior.
- `RunRequest.APIKey` is request-scoped and direct; it is used only for generation and is stripped from public results.
- internal errors are mapped to public sentinels in `errors.go`.
## Failure Behavior
Adapters should:
- keep external error payloads concise and stable.
- avoid leaking raw secret values.
- use sentinels and typed errors for mapping.
- preserve strict external input decoding.
- keep validation content failures distinct from runtime errors.
CLI writes human-readable summaries to stderr. HTTP writes JSON error envelopes. The public Go facade returns typed errors.
## State And Manifests
Adapters do not add durable run state.
- No adapter writes run manifests.
- No adapter implements checkpoint, skip, or resume behavior.
- CLI output files are caller-selected artifacts, not internal state.
## Tests To Inspect
Inspect focused tests when changing this area:
- `internal/adapter/cli/run_test.go`
- `internal/adapter/http/handler_test.go`
- `engine_test.go`
- `internal/format/prepared_run_test.go`
- `internal/llm/openai_compatible_client_test.go`
Run the affected adapter package tests and recheck the relevant canonical
contract. The [testing policy](../policy/testing.md) owns global test
sufficiency guidance.
## Change Recipes
@@ -154,33 +92,22 @@ Adapters do not add durable run state.
precedence while wiring it through its consuming adapter.
3. Add focused configuration and adapter tests for parsing, mapping, and
effective behavior.
4. Update the [configuration contract](../config.md) and any external contract
affected by the new behavior.
4. Update the [configuration contract](../config.md) and any affected external
contract.
### CLI Flags
1. Add the flag to the relevant command in `internal/adapter/cli/run.go`.
1. Add the flag to the relevant parser in `internal/adapter/cli/run.go`.
2. Keep command scope and application-configuration precedence intentional.
3. Add or update parser and command tests in
`internal/adapter/cli/run_test.go`.
4. Update the [CLI contract](../cli.md) and any maintained examples affected by
the invocation.
4. Update the [CLI contract](../cli.md) and affected maintained examples.
### Adapter Capabilities
1. Define or reuse the appropriate domain or use-case interface boundary.
2. Implement translation and IO behavior in the adapter without moving
use-case decisions out of `internal/usecase`.
2. Implement translation and IO behavior without moving use-case decisions out
of `internal/usecase`.
3. Add focused mapping, parsing, and error-behavior tests.
4. Update this document and the affected canonical public or integration
contract. Update [source internals](sources.md) when source-loading behavior
changes.
## Architectural Invariants
- Adapter packages stay thin and translation-focused.
- App config is resolved before dependency construction.
- External input strictness is part of contract stability.
- CLI and HTTP construct runners without a repairer.
- HTTP endpoint details remain canonical in `docs/api.md`.
- Public Go package details remain canonical in `docs/consumers/pkg-scriptorium.md`.
4. Update this document and the affected public or integration contract. Update
[source internals](sources.md) when source-loading behavior changes.

85
docs/internal/llm.md Normal file
View File

@@ -0,0 +1,85 @@
# LLM Internals
## Purpose
`internal/llm` defines the provider-neutral `Client` interface and the
OpenAI-compatible client implementation. The [OpenAI-compatible integration
contract](../integrations/openai-compatible-chat.md) owns the outbound HTTP wire
format and protocol behavior.
## Construction
`NewOpenAICompatibleClient` validates a non-empty configured base URL, records
an optional default model, and establishes the default timeout. A non-positive
configured timeout uses the internal default.
When callers supply an `http.Client`, construction clones it rather than
mutating the caller's instance. A supplied client with no timeout receives the
resolved default in the clone; a supplied non-zero timeout is retained. The
client stores the trimmed base URL, default model, timeout, and cloned client.
## Generate Flow
`Generate` receives a `domain.GenerateRequest` from the runner:
1. validate the effective timeout and choose the request endpoint;
2. map the domain request to the internal wire-request representation;
3. validate and flatten extra parameters, encode JSON, and create the HTTP
request;
4. prefer a direct API key, otherwise resolve the configured key environment
variable;
5. derive a request HTTP client when an explicit timeout changes the configured
client;
6. execute the request, reject non-success status responses without returning
provider response bodies; and
7. decode the response subset into `domain.GenerateResponse`.
`openAIChatRequestFromGenerateRequest` is the conversion boundary for effective
model defaults, explicit numeric-presence state, rendered messages, structured
output, and session-ID validation. `openAIChatRequestPayload` protects reserved
fields and JSON encoding before an HTTP call. The external payload shape is
defined only in the [integration contract](../integrations/openai-compatible-chat.md).
## Error Categories
The package uses these internal sentinels:
- `ErrInvalidConfig` for invalid client construction;
- `ErrInvalidRequest` for invalid effective generation input;
- `ErrRequestFailed` for request construction or transport failures;
- `ErrUnexpectedStatus` for non-success HTTP responses; and
- `ErrMalformedResponse` for invalid or incomplete successful-response data.
The runner maps an invalid LLM request to its invalid-request category and
other LLM failures to its generation category. Adapters then apply their public
error contracts.
## Package-Local Guarantees
- The default-model fallback happens before wire encoding.
- Per-request timeout handling clones a configured HTTP client when needed; it
does not mutate shared client state.
- Direct API keys take precedence over environment lookup within this client.
- Provider response bodies are discarded for non-success status responses.
- The client does not implement retries, tool calls, or a stateful session
store.
## Verification And Change Recipe
Inspect:
- `internal/llm/openai_compatible_client_test.go`
- `internal/usecase/runner_test.go`
- `internal/adapter/http/handler_test.go`
When changing the client:
1. keep domain-to-wire mapping inside `internal/llm` and preserve the `Client`
interface;
2. test construction, timeout selection, mapping, and error categorization;
3. update the [OpenAI-compatible integration contract](../integrations/openai-compatible-chat.md)
for any observable wire or protocol change; and
4. update [runner internals](runner.md) if the client boundary or structured
output handoff changes.
The [testing policy](../policy/testing.md) owns global test sufficiency.

View File

@@ -41,7 +41,7 @@ and invariants; public behavior belongs in the linked contracts.
| --- | --- | --- |
| `internal/format` | Formats 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), [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) |
Focused internal documents describe the components that have detailed
orchestration, adapter, or source behavior. Package tests live alongside the

View File

@@ -2,145 +2,118 @@
## Purpose
`internal/usecase.Runner` is the core prompt-execution orchestrator. It prepares prompt requests, calls the configured LLM client for `Run`, validates generated output, and returns domain results.
`internal/usecase.Runner` is the prompt-execution orchestrator. It prepares
domain requests, invokes an injected LLM client, validates output, and returns
domain results. Transport parsing, response mapping, and public type conversion
remain outside this package.
Transport parsing, DTOs, CLI output, HTTP status mapping, and public package type conversion belong outside the runner.
The [configuration reference](../config.md) owns prompt, profile, schema, and
runtime-setting definitions. Public error behavior is defined by the
[HTTP API](../api.md) and [Go package](../consumers/pkg-scriptorium.md)
contracts.
## Inputs And Outputs
## Dependencies And Construction
Primary inputs:
`Runner` receives these collaborators:
- `domain.RunRequest`
- repositories/readers/renderers/validators injected at construction
- `context.Context` for cancellation
- `promptdef.Repository`;
- `profile.Repository`;
- `artifact.Reader`;
- `prompt.Renderer`;
- `llm.Client`;
- `validate.Validator`; and
- an optional `OutputRepairer`.
Primary outputs:
- `domain.PreparedRun` from `Prepare`
- `domain.RunResult` from `Run`
- wrapped sentinel errors for adapter mapping
LLM boundary types:
- `domain.GenerateRequest`
- `domain.GenerateResponse`
## Dependencies
`Runner` depends on package interfaces instead of concrete adapter types:
- `promptdef.Repository`
- `profile.Repository`
- `artifact.Reader`
- `prompt.Renderer`
- `llm.Client`
- `validate.Validator`
- optional `usecase.OutputRepairer`
The CLI, HTTP adapter, and public Go package construct these dependencies and pass them in.
## Config Fields
`Runner` does not read app config files. Effective behavior is determined by injected dependencies and the `domain.RunRequest`.
Adapter wiring commonly reflects these app config fields:
- `prompt_dir`
- `profile_dir`
- `schema_dir`
- `server.artifact_root`
- HTTP request/artifact/response size limits
Runtime model settings are resolved from the selected profile plus request overrides.
`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.
## Prepare Flow
`Prepare`:
`Prepare` performs one deterministic preparation pass for a request:
1. requires a non-empty prompt ID.
2. loads the prompt definition and computes its hash.
3. selects the profile from request `profile_id`, then prompt `default_profile`.
4. loads the selected execution profile.
5. merges built-in execution defaults, profile values, and request overrides.
6. applies request-scoped direct API key values for public Go callers.
7. validates endpoint, model, and credential requirements.
8. resolves the output contract and JSON Schema document when required.
9. reads input artifacts.
10. renders prompt messages and hashes the rendered prompt.
11. returns a prepared run without calling the LLM.
1. validate the prompt ID and load the prompt definition;
2. hash the definition and select the explicit or default profile;
3. load the profile and resolve effective execution settings;
4. validate endpoint, model, and credential availability;
5. resolve the output contract and, for JSON Schema output, load a structured
schema document before model execution;
6. read and hash input artifacts;
7. render messages and the session ID; and
8. return a `PreparedRun` containing the effective state and rendered-prompt
hash.
Numeric request overrides are presence-aware: omitted values preserve the current effective value, while explicit zero values are real overrides.
Execution settings merge defaults, profile values, and a request override.
Numeric override presence is retained so explicit zero values are not confused
with omissions.
## Run Flow
## Run And Validation Flow
`Run`:
`Run` creates a run ID and timestamps, then calls `Prepare` rather than
duplicating preparation. It sends the prepared prompt, effective target,
target-presence state, and optional structured-output specification to the LLM
client. It converts the returned content to an output artifact, validates it,
and returns the artifact, validation, hashes, usage, and timing metadata.
1. creates a run ID and start timestamp.
2. calls `Prepare`.
3. calls the injected LLM client with rendered messages, effective target, target presence, and structured-output settings.
4. builds the output artifact.
5. validates the output.
6. optionally attempts bounded repair when a repairer is injected and the contract permits repair.
7. returns the run result with artifact, raw output, validation, hashes, selected profile/model metadata, usage, and timing.
A validator can return a content result or an operational error. Content
failures stay in the result; schema loading, compilation, and validator
operational failures are returned as `ErrValidation`. The canonical distinction
for callers is documented by the public contracts.
`Run` must reuse `Prepare`; prepare logic should not be duplicated elsewhere.
## Repair Boundary
## Validation And Repair
Repair is an internal optional loop. It starts only when a repairer is present,
the output contract permits one or more attempts, validation failed, and the
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.
Validation content failures are returned as successful run results with `Validation.Status == failed`. They are not runtime errors.
`NewDefaultOutputRepairer` delegates to the injected LLM client. CLI, HTTP, and
the public engine use `NewRunner` and therefore do not inject this repairer.
Validation runtime failures, such as schema load or compile errors, return `ErrValidation`.
## Error Translation
Repair attempts occur only when all conditions are true:
- a repairer is injected
- `repair_attempts` is greater than zero
- validation status is `failed`
- validation mode is `json` or `json_schema`
CLI and HTTP wiring call `usecase.NewRunner(...)`, which does not inject a repairer. Normal CLI and HTTP execution therefore does not repair invalid output.
## Failure Behavior
Stable runner sentinels include:
Runner sentinels identify failure categories for adapters:
- `ErrInvalidRequest`
- `ErrProfileRequired`
- `ErrAPIKeyEnvMissing`
- `ErrAPIKeyRequired`
- `ErrPromptLoad`
- `ErrProfileLoad`
- `ErrArtifactLoad`
- `ErrAPIKeyEnvMissing` and `ErrAPIKeyRequired`
- `ErrPromptLoad`, `ErrProfileLoad`, and `ErrArtifactLoad`
- `ErrPromptRender`
- `ErrLLMGenerate`
- `ErrValidation`
Adapters should use `errors.Is` against sentinels and lower-level repository errors instead of matching message text.
Wrap errors with those sentinels and preserve their identities through
`errors.Is`; adapters must not classify errors by message text. The runner
passes direct keys only to the LLM boundary and never includes resolved key
values in prepared or run results.
Secret values must not appear in prepared output, run results, logs, HTTP responses, or serialized public package results. The effective API-key environment-variable name may appear.
## Package-Local Guarantees
## State And Manifests
- `Run` always reuses `Prepare`.
- Schema documents are loaded before the initial LLM call when structured output
is required.
- Output validation records attempts used, including repair attempts.
- Runner state is per request; the package does not create a durable run store
or manifest.
- Source, renderer, validator, and LLM implementations remain injected
boundaries.
The runner is stateless across requests.
## Verification And Change Recipe
- No durable run store.
- No manifest files.
- No checkpoint, skip, or resume behavior.
- Recovery is a new request after correcting inputs, config, or environment.
## Tests To Inspect
Inspect:
- `internal/usecase/runner_test.go`
- `internal/usecase/integration_test.go`
- `engine_test.go`
- `internal/adapter/cli/run_test.go`
- `internal/adapter/http/handler_test.go`
## Architectural Invariants
When changing orchestration:
- Use-case decisions stay in `internal/usecase`.
- `Run` reuses `Prepare`.
- Prompt/profile/artifact/schema loading remains behind injected boundaries.
- Validation content failures are result state; validation runtime failures are errors.
- Repair loops are bounded by `repair_attempts` and repairer presence.
- Resolved secret values are never serialized or emitted.
1. identify the collaborator boundary and the affected `Prepare` or `Run` state;
2. preserve the `Run`-through-`Prepare` path and error identity;
3. add focused runner or integration tests for changed state transitions,
validation, or repair behavior; and
4. update the owning external contract and any affected source or LLM internal
document.
The [testing policy](../policy/testing.md) owns global test sufficiency.

View File

@@ -2,142 +2,79 @@
## Purpose
This document covers implemented prompt, profile, schema, artifact, and catalog source behavior. It is for developers changing loaders or source wiring.
This document describes how source packages load prompt definitions, profiles,
schemas, and artifacts. The [configuration reference](../config.md) owns their
user-facing formats and settings. The [HTTP API reference](../api.md) owns
HTTP-visible artifact outcomes; [operations](../operations.md) owns deployment
handling.
Full user-facing YAML and config reference material belongs in `docs/config.md`.
## Prompt Definitions
## Prompt Definition Sources
`internal/promptdef` provides filesystem and `fs.FS` repositories. Both use
`internal/filecatalog` for recursive YAML discovery, deterministic ordering,
display paths, and root cleaning.
`internal/promptdef` provides directory-backed and `fs.FS` repositories.
Repositories select a prompt by YAML ID and optional version rather than by
path. They decode through strict YAML handling, reject duplicate matching
definitions, and resolve `content_file` relative to the definition. The `fs.FS`
implementation resolves content paths inside its source root; absolute paths and
traversal outside that root are rejected before file access.
Behavior:
## Profiles And Built-Ins
- recursively scans `.yaml` and `.yml` files.
- decodes YAML with known-fields checking.
- looks up prompts by YAML `id`, not by path.
- optionally filters by prompt `version`.
- rejects duplicate matching prompt IDs.
- requires `id`, `version`, and at least one message.
- requires each message to set exactly one of `content` or `content_file`.
- resolves filesystem `content_file` values relative to the prompt YAML file.
- resolves `fs.FS` `content_file` values inside the configured source root.
- permits prompt subdirectories only as organization; they are not part of prompt identity.
`internal/profile` provides filesystem, `fs.FS`, and overlay repositories.
`internal/profile/builtin` exposes embedded assets through the same repository
interface.
For `fs.FS` roots, absolute paths and relative traversal outside the source root are rejected by catalog path helpers.
An overlay asks its primary source first. It falls back only when the primary
reports `ErrProfileNotFound`; invalid YAML, duplicate IDs, validation failures,
and raw-key failures are returned rather than hidden by fallback. This makes a
custom ID override a built-in ID while retaining errors in the custom source.
## Profile Sources
The public engine can overlay in-memory profiles ahead of both file-backed and
built-in repositories. Profile field definitions, validation ranges, and the
built-in catalog remain in the [configuration reference](../config.md).
`internal/profile` provides directory-backed, `fs.FS`, and overlay repositories. `internal/profile/builtin` embeds built-in profile YAML assets and exposes them through the same repository interface.
## Schemas
Behavior:
`internal/validate` supplies `StandardValidator` for filesystem sources and
`FSValidator` for `fs.FS` sources. Directory-backed validation loads the named
schema path; it does not search directories by basename. `fs.FS` schema paths
are cleaned and checked against their configured root, while a single-file
source matches its file base name.
- recursively scans `.yaml` and `.yml` files.
- decodes YAML with known-fields checking.
- looks up profiles by YAML `id`, not by path.
- rejects duplicate IDs inside the same source.
- rejects raw `api_key` fields in YAML; file-backed profiles must use `api_key_env`.
- validates required `endpoint` and `model` values.
- validates numeric profile ranges.
The runner requests a schema document before generation when it needs
structured output. JSON and schema mismatches in generated content are
validation results; source access, decoding, registration, and compilation
failures are operational errors.
Overlay behavior:
## Artifacts
- custom profiles are primary.
- built-in profiles are fallback.
- fallback occurs only after a primary `ErrProfileNotFound`.
- primary validation, YAML, duplicate, and raw-key errors are returned directly.
- duplicate IDs across custom and built-in sources are allowed because the custom profile overrides the built-in one.
`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. The restricted composite reader used by the HTTP adapter combines
inline reading with a rooted file reader and optional byte limit.
The public Go facade can add in-memory profiles ahead of file-backed and built-in profiles.
The rooted reader cleans paths and applies lexical containment without resolving
symlinks. It checks relative references against the configured root and accepts
absolute references only when they remain inside that lexical root. The OS still
follows symlinks after that check. The public containment outcome is documented
by the [HTTP API reference](../api.md); deployment permissions belong in
[operations](../operations.md).
## Schema Sources
## Failure Boundaries
`internal/validate` provides:
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.
- `StandardValidator` for filesystem paths.
- `FSValidator` for `fs.FS` roots and single-file public schema sources.
Source reads use current filesystem or `fs.FS` content for each request. These
packages create no manifests, checkpoints, or durable run state.
Behavior:
## Verification And Change Recipe
- `json_schema` validation requires a non-empty `schema_path`.
- filesystem schema paths resolve relative to `schema_dir` unless absolute.
- directory-backed schema lookup uses the explicit `schema_path`; it does not search recursively by basename.
- `fs.FS` schema paths must remain inside the configured source root.
- single-file schema sources match by the configured file base name.
- schema documents are loaded before the LLM call for structured output.
- JSON parse failures are validation content failures.
- schema access, decode, registration, and compile failures are runtime validation errors.
## Artifact Sources
`internal/artifact` supports two input artifact reference types:
- `inline`
- `file`
Inline behavior:
- requires a non-empty body.
- produces text/plain artifacts.
- hashes the body bytes.
Direct file behavior:
- used by CLI `run`, CLI `render`, and the public Go facade.
- requires a non-empty URI.
- reads from the process filesystem without HTTP artifact-root restrictions.
- infers content type from file extension, defaulting to text/plain.
Restricted file behavior:
- used by HTTP `serve`.
- allows inline artifacts even when no artifact root is configured.
- denies file artifacts when no artifact root is configured.
- resolves relative file URIs against `server.artifact_root`.
- accepts absolute file URIs only when they pass containment checks.
- applies `server.max_artifact_bytes` when configured.
Restricted containment is lexical. It cleans paths and checks the relative path against the configured root; it does not resolve symlinks. Symlinks inside the root are followed by the operating system, including symlinks that target files outside the root.
## Catalog Helpers
`internal/filecatalog` centralizes shared source helpers:
- recursive YAML discovery for filesystem and `fs.FS` roots.
- deterministic sorting.
- `.yaml` and `.yml` filtering.
- display paths for diagnostics.
- YAML file stems.
- `fs.FS` root cleaning and containment checks.
Repository code should use these helpers instead of reimplementing path traversal and containment rules.
## Failure Behavior
Common source failures:
- missing prompt/profile/schema/artifact files.
- invalid YAML or JSON.
- unknown YAML fields.
- duplicate prompt or profile IDs.
- prompt/profile validation errors.
- raw API key fields in profile YAML.
- unsupported artifact reference type.
- missing inline body or file URI.
- artifact outside HTTP root.
- artifact exceeding HTTP size limit.
- schema load or compile failure.
Prompt/profile repository lookup errors are mapped by adapters separately from runtime runner errors. Validation content failures remain result state; source and schema runtime failures return errors.
## State And Manifests
Source packages do not persist run state.
- No manifests are read or written.
- No source package implements skip or resume behavior.
- Source reads reflect the current filesystem or `fs.FS` state for each request.
## Tests To Inspect
Inspect:
- `internal/promptdef/repository_test.go`
- `internal/profile/repository_test.go`
@@ -147,23 +84,14 @@ Source packages do not persist run state.
- `internal/usecase/integration_test.go`
- `engine_test.go`
## Change Recipe
When updating prompt, profile, schema, or built-in assets:
When updating prompt, profile, schema, or built-in-profile assets:
1. keep assets valid for the strict loader and the relevant source boundary;
2. update the [configuration reference](../config.md) when a file-format,
catalog, or default changes;
3. run focused source and integration tests, including the built-in repository
test when embedded assets change; and
4. update this document when discovery, precedence, containment, or failure
mechanics change.
1. Keep files valid for their strict loader and source boundary.
2. Keep maintained examples and fixtures secret-free.
3. Run focused prompt, profile, schema, or validation tests for the changed
source.
4. Update the [configuration contract](../config.md) and every affected
external contract; update this document when loading or precedence mechanics
change.
## Architectural Invariants
- Prompt/profile identity comes from YAML `id`.
- External YAML decoding remains strict.
- File-backed profile YAML never accepts raw API key values.
- Built-in profiles are fallback, not a replacement for custom source validation.
- HTTP file artifacts remain rooted by lexical containment.
- Schema runtime failures remain errors, while JSON/schema content mismatches remain validation results.
The [testing policy](../policy/testing.md) owns global test sufficiency.