12 KiB
Internal Sources And Validation
Purpose
This document describes Promptkit's implemented internal source, artifact, rendering, and output-validation behavior. The architecture policy owns the library boundary and dependency rules. None of these internal packages is a supported consumer API, and the internal engine assembles them behind the public facade's source options and values. The framework format reference owns the exact file fields, validation modes, built-in catalog, and source precedence.
Typed Configuration Loading
internal/configuration now contains a private ConfigKit adapter for keyed
backend, profile, and prompt mappings. It performs one layered load, records
effective field presence (including explicit null), normalizes backend and
profile fields through ConfigKit resolution, and validates final backend and
profile domain values. A lower-layer domain value replaced before the handoff
is not separately validated. It also validates effective prompt fields, reads
referenced message content using the contributing field's passive source
context, checks static template syntax, and indexes immutable definitions by
exact ID and version. The loader also builds an immutable backend registry from
the final definitions and indexes profile execution targets after inheritance,
applying the selected backend's defaults only then. These targets retain
explicit numeric-zero presence and null suppression for runtime execution.
Its focused tests exercise real ConfigKit loading.
This loader is not yet wired into engine assembly. The public engine continues
to use the source and repository behavior described below. The public
DirectoryInputs and FilesystemInputs helpers select sorted YAML inputs for
one strict-composition group and attach an explicit resource root; they do not
assign precedence or enable imports.
Prompt Definitions
internal/promptdef uses one source-neutral flow for prompt selection and
normalization. That flow scans normalized YAML ID and version metadata,
requires one strictly decoded document per file, classifies errors for the
selected definition, detects duplicates, and normalizes the exact match.
Small operating-system and fs.FS adapters own discovery, byte reads, display
paths, content opening, and root containment. Each lookup remains a
point-in-time scan: definitions and catalogs are not cached, and file-backed
message content is opened only for the exact selected candidate.
Message roles are normalized through the shared domain owner by trimming
Unicode whitespace and lowercasing. Only developer, system, user, and
assistant are published; invalid roles remain selected prompt-definition
failures rather than becoming request errors. Cache-control metadata uses the
same shared domain normalization and defensive-copy rule.
Operating-system sources enforce containment against canonical roots and
targets so symlinks cannot escape. Injected fs.FS sources enforce containment
in their clean relative path namespace. A single-file source uses the selected
prompt file's containing directory as its root. Every content path must be
relative and is opened from its exact parsed text after a separate blank check;
contained parent components and whitespace-bearing names remain valid.
Exact prompt inspection performs one point-in-time lookup through that same repository and validates referenced message content before returning declared metadata. It does not parse templates or read profile, input, or schema sources, and it does not retain the definition for a later execution.
Its package tests own prompt selection, strict decoding, definition validation, duplicate detection, and source containment: prompt-definition repository tests.
Profiles And Built-Ins
internal/profile owns immutable in-memory and filesystem repositories, local
validation, overlays, and resolution for execution profiles. In-memory
definitions are normalized and validated once at that repository boundary and
are defensively copied on storage and lookup. A file contains
exactly one YAML document and its trimmed YAML id is its only selection
identity; filenames do not confer authority. Each point lookup reads discovered
files once for their metadata and reuses the selected file's bytes for strict
decoding; unrelated profiles are not fully decoded. Strict selected decoding
recognizes base_profile and the optional backend field, trims their values,
and permits inherited target fields only when a base is named. File-backed
extra_params values are validated and defensively copied through the shared
bounded JSON-value owner before a profile is published. OpenAI-compatible
reserved-field policy remains with the model-client and backend-registry owners.
LoadFSRepository is the eager immutable loading boundary for internal
catalog consumers. It discovers and strictly validates every raw profile once,
preserves safe source metadata including explicitly present YAML fields, and
publishes independently copied values from memory. It does not resolve profile
inheritance. Configured consumer sources continue to use the lazy point lookup
repositories described above.
internal/catalog selects the released keyed OpenRouter and Rakestrawhome
assets as one ConfigKit underlay and PromptKit-owned profiles as the next.
It discovers YAML inputs and supplies passive filesystem context without
parsing or pre-validating catalog definitions. The configuration loader
validates only the final effective backend and profile values, including
consumer overlays of maintained bases or backends. The new path has no catalog
source-layout, ownership-prefix, connection-field, or secret-key rejection.
Until public engine assembly switches to that loader, internal/legacycatalog
supports the existing public construction path with the published flat /v2
assets. The repository behavior below describes that temporary path.
The overlay repository consults the next repository only when the higher-precedence repository reports that a profile is absent. A reliably selected malformed profile stops fallback, while an unrelated malformed file does not become authoritative through its filename. Loading does not check backend registry membership because the available registry belongs to the assembled engine; the runner checks membership during preparation and exact profile inspection.
The internal engine assembles one raw composite catalog in precedence order:
in-memory profiles, one ordinary configured source, an application fallback
source, Promptkit-owned profiles, then the maintained external catalog. It
constructs the owned definitions through the in-memory repository and validates
their maintained-only resolution and backend membership before consumer sources
are overlaid. An explicit file or fs.FS profile source replaces
Config.ProfileDir within the ordinary configured-source category. One outer
resolving repository wraps that complete raw catalog, so each base lookup
observes the same precedence and shadowing rules.
The resolving repository traverses every selected chain afresh, retains no
cache, detects cycles, limits a chain to 32 profiles, merges root-to-leaf into a
new caller-owned value, and validates the final target before publishing it. It
does not check backend registry membership. Exact base_profile syntax, merge
rules, and consumer-visible failure behavior belong to the framework format
reference.
Exact profile inspection performs one point-in-time resolved lookup through those profile sources and checks the final target without reading prompt, input, or schema sources. It does not retain that lookup for a later execution. Prepared execution instead freezes the fully resolved target; a later ordinary operation performs a fresh traversal.
The imported external catalog provides external profiles and matching backend definitions. The internal engine owns the Promptkit profiles documented in the framework format reference. Profile loading and overlay behavior are owned by the profile repository tests, while catalog completeness, backend selection, and duplicate IDs are owned by the catalog adapter tests.
Ordinary Artifacts
internal/artifact accepts explicitly typed inline references even when their
body is empty. It also resolves unrestricted, caller-selected paths only when
they identify regular operating-system files, checking that condition before
and after opening the file. It copies content into an artifact, records
metadata and an opaque content-equality value, and applies a content-type
fallback.
Regular files are read synchronously in bounded chunks. Cancellation is checked before opening, before and after every read, and before publishing the artifact, so a canceled read never publishes partial content. The ordinary reader does not detach file reads into background goroutines.
This ordinary reader does not implement an inbound HTTP security boundary. In particular, it does not constrain files to an application root or impose an HTTP request-size policy. Scriptorium's restricted HTTP reader remains an application concern outside Promptkit. The artifact reader tests own the implemented reader behavior and failures.
Rendering
internal/prompt renders definition messages as Go templates using named
artifacts and variables. Within one render, each referenced artifact body is
converted to text lazily and cached by input name for reuse across the session
and every message; the cache is not shared across renders. Conversion uses
bounded chunks and preserves the artifact bytes exactly.
Session and message parsing and execution remain synchronous. The renderer checks cancellation before and after each parse and execution boundary, between artifact conversion chunks, around each message, and before publishing the complete prompt. It cannot interrupt template work already in progress and never publishes a partial prompt after observing cancellation. It validates and canonicalizes message roles before carrying roles, session IDs, and cache control into the rendered prompt. The renderer tests own rendering behavior.
Schemas And Output Validation
internal/validate provides validators backed by an operating-system
filesystem or an fs.FS. Invalid generated content is returned as a validation
result; inability to load, register, or compile a schema is an operational
error.
Every preparation operation creates one operation-local validation plan. None, basic, and JSON modes retain the effective output contract without source access. JSON Schema mode loads the root document once, resolves and compiles each transitive reference, and retains the compiled validator. Schema compiler resources use canonical escaped file or private-scheme URLs; loaders decode their paths once and enforce the configured source boundary. The provider-facing structured-output metadata uses the root document captured by the same plan.
Schema preparation and execution remain synchronous. Promptkit checks
cancellation before and after source resolution, JSON decoding, compilation,
and validation, and between bounded schema-read chunks. Once cancellation is
observed it returns the context error without publishing a partial plan or
validation result, even when a compiler or validator has just returned a
different error or a successful result. An fs.FS method or JSON Schema
dependency call already in progress cannot be preempted; Promptkit waits for
that call to return and then gives cancellation precedence. Validation does
not detach dependency work into background goroutines.
Prepare discards its validation plan after returning metadata. Run retains
its plan for initial and repaired-output validation, then discards it with the
operation. PrepareExecution retains the plan in its private frozen payload;
RunPrepared uses that plan without reopening prompt, profile, input, or
schema sources or rerendering the request. A later ordinary Run always
performs fresh source resolution and preparation.
The validator tests own basic, JSON, JSON Schema, source resolution, schema loading, compilation, frozen-reference behavior, content-failure behavior, and the synchronous cancellation boundary. Prepared execution orchestration is owned by the use-case tests.