Prepare the v0.9.0 release

This commit is contained in:
2026-08-26 17:31:53 +00:00
parent 81f41564a2
commit a4264bf4b4
5 changed files with 129 additions and 1334 deletions

View File

@@ -33,7 +33,10 @@ boundary and constraints that framework work must preserve.
## Release Guidance
Consumers upgrading from `v0.7.0` to `v0.8.0` should read the
Consumers upgrading from `v0.8.0` to `v0.9.0` should read the
[v0.9.0 changelog and migration guide](docs/releases/v0.9.0.md).
Consumers upgrading from `v0.7.0` to `v0.8.0` can consult the
[v0.8.0 changelog and migration guide](docs/releases/v0.8.0.md).
Earlier adopters can consult the

125
docs/releases/v0.9.0.md Normal file
View File

@@ -0,0 +1,125 @@
# Promptkit v0.9.0
This supplemental changelog and migration guide summarizes the consumer-facing
changes from `v0.8.0` to `v0.9.0`. The annotated `v0.9.0` tag is the
authoritative release record. Exact current contracts belong to the linked
GoDoc and durable documentation.
## Summary
`v0.9.0` adds stateless request-message composition and separates maintained
provider data from Promptkit's core implementation:
- callers can append already-rendered messages to a configured prompt for
application-owned conversations and semantic correction workflows;
- the public package now publishes constants for the four supported text-chat
roles, and prompt definitions use the same normalized role vocabulary; and
- the OpenRouter and Rakestrawhome backend/profile catalogs now come from two
independently versioned Go module dependencies.
## Compatibility
This release adds one field and four constants to the public API and removes no
public declarations. Existing keyed `RunRequest` literals that omit
`AppendedMessages` retain their behavior. Adding the field changes the struct
shape, so consumers using positional `RunRequest` literals must convert them
to keyed literals.
Message roles in prompt definitions are now trimmed, lowercased, and required
to be `developer`, `system`, `user`, or `assistant`. Definitions using another
role that earlier releases accepted as an arbitrary nonblank string now fail
prompt loading. In particular, the text-only message contract does not support
`tool` or the deprecated `function` role. Otherwise valid roles with different
case or surrounding whitespace are normalized rather than rejected.
The catalog extraction preserves Promptkit's public API, built-in backend and
profile IDs, configuration, precedence, credential handling, and capacity
behavior. Consumers do not import or register either catalog themselves, and
no configuration migration is required. Promptkit now selects two
independently versioned data dependencies and guarantees only the catalog
versions selected and tested by this Promptkit release.
## Upgrade
Update the module dependency with:
```sh
go get gitea.maximumdirect.net/eric/promptkit@v0.9.0
go mod tidy
```
Convert any positional `RunRequest` literals to keyed literals. Review prompt
definitions for unsupported roles, then run the consuming project's ordinary
and race-enabled tests.
## Appended Request Messages
`RunRequest.AppendedMessages` accepts caller-owned `RenderedMessage` values
that Promptkit validates, copies, and appends after every rendered
prompt-definition message in caller order. Promptkit does not template this
content, retain conversation state between calls, impose a retry policy, or
apply a message-count, byte-size, token, or context-window limit. Upstream
rejections continue through the ordinary generation-error boundary.
This primitive supports application-owned conversation continuations and
domain-aware correction loops while preserving Promptkit's existing
preparation, hashing, prepared-execution, structural repair, backend-capacity,
credential, and cancellation behavior. Appended content can include sensitive
model output or application feedback; prepared values expose the complete
effective messages by design, while default request formatting reports only
the appended-message count.
See the
[consumer appended-message example](../consumers/pkg-promptkit.md#append-already-rendered-messages),
the [`RunRequest`, `RenderedMessage`, and `CacheControl` GoDoc](../../types.go),
and the [OpenAI-compatible request contract](../integrations/openai-compatible-chat.md#request-body)
for current behavior.
## Supported Message Roles
The new `RoleDeveloper`, `RoleSystem`, `RoleUser`, and `RoleAssistant`
constants identify the complete role vocabulary accepted by Promptkit's
text-chat message model. The same validation and normalization now apply to
prompt-definition messages and request-supplied appended messages. Promptkit
does not translate between roles; provider- or model-specific rejection of an
otherwise supported role remains an upstream generation error.
See the [message format reference](../formats.md#messages-and-templates) for
the canonical prompt-definition contract.
## Independently Versioned Backend Catalogs
Promptkit imports immutable catalog data for its maintained OpenRouter and
Rakestrawhome backends and profiles. Engine construction validates and
assembles both catalogs behind the existing built-in registry and profile
precedence rules. Promptkit no longer keeps duplicate embedded profile assets
or hard-coded definitions for those maintained backends.
The module versions in Promptkit's `go.mod` identify the catalog releases
tested with this release. The [built-in backend and profile format
reference](../formats.md#built-in-backends) remains the canonical consumer
contract, while the [internal source documentation](../internal/sources.md#profiles-and-built-ins)
describes the dependency boundary.
## Public API Changes
The release adds:
- `RunRequest.AppendedMessages`;
- `RoleDeveloper`;
- `RoleSystem`;
- `RoleUser`; and
- `RoleAssistant`.
No public declaration was removed.
## Consumer Action
- Convert positional `RunRequest` literals to keyed literals.
- Replace unsupported prompt-definition roles with an appropriate supported
role, or keep richer tool-call protocols in an application-owned client.
- Treat appended messages and prepared effective messages according to the
application's sensitive-data policy.
- Do not add direct catalog imports or registration calls; existing Promptkit
construction and configuration remain correct.
- Run consumer ordinary and race-enabled tests after updating the module.

View File

@@ -1,434 +0,0 @@
# Appended Request Messages
## Status
Accepted for future implementation. This roadmap records a concrete downstream
requirement from Notarius and the Promptkit-native design selected for it. It
describes future behavior only; the current public API and canonical
documentation remain authoritative until the feature is implemented.
## Purpose
Allow a Go consumer to execute an ordinary configured prompt with an explicit
sequence of already-rendered chat messages appended after the
prompt-definition messages. The capability should preserve Promptkit's normal
preparation, inspection, hashing, validation, structured-output repair,
capacity, cancellation, credential, error, and debug boundaries.
The immediate consumer is Notarius, which needs to resubmit the same configured
prompt after semantic validation rejects an LLM-produced stage candidate, then
append:
1. an `assistant` message containing the exact defective response; and
2. a `user` message containing bounded application-owned feedback and a request
for one corrected replacement response.
Promptkit should provide the safe message-composition mechanism. Notarius
should continue to own semantic validation, stage retry budgets, correction
wording, terminal policy, and the decision to start another completion.
## Consumer Need
Promptkit v0.8 can perform bounded structural repair inside one `Run` or
`RunPrepared` operation. Its repairer correctly starts from the complete
original rendered prompt, appends the latest candidate as an assistant turn,
and adds a corrective user turn. That loop is intentionally driven by
Promptkit's own structural validators.
Notarius has a distinct outer workflow:
1. Promptkit returns a structurally valid response.
2. Notarius materializes a typed stage candidate.
3. One or more application validators evaluate domain semantics.
4. If the candidate is rejected and a Notarius stage attempt remains, Notarius
makes a new Promptkit request with application-owned corrective turns
appended after the ordinary configured prompt.
Each `Run`, `Prepare`, or `PrepareExecution` remains an independent operation
that resolves current sources and inputs. Promptkit does not retain or reuse a
base prompt across calls, so the ordinary prefix is identical across attempts
only when the selected definitions, inputs, variables, and other preparation
inputs remain identical. A consumer that requires a pre-execution equality
check can prepare each attempt and compare its opaque rendered-prompt hash
before calling `RunPrepared`. A reusable prepared conversation or frozen
cross-run prompt is outside this feature.
Today a `RunRequest` can select and parameterize a prompt definition, but it
cannot append downstream-owned messages. Registering a second prompt manifest
for every correction-capable operation would duplicate prompt identity,
profile defaults, input declarations, output contracts, and message assets.
Calling an injected model client directly would bypass Promptkit preparation,
capacity, structured output, repair, error adaptation, and provenance. Neither
workaround is a suitable long-term consumer contract.
## Target Public API
Add an append-only field to `RunRequest`:
```go
type RunRequest struct {
// Existing fields omitted.
AppendedMessages []RenderedMessage
}
```
Retain `RenderedMessage.Role` as a string for source compatibility and expose
the untyped string constants `RoleDeveloper`, `RoleSystem`, `RoleUser`, and
`RoleAssistant`. Their values are `developer`, `system`, `user`, and
`assistant`, respectively. The supported values and their semantics are part
of the public contract.
`AppendedMessages` contains fully rendered message values. Promptkit normalizes
and validates their roles as described below, but does not interpret their
content as Go templates, resolve input references inside it, or load it from
files. Nil and empty slices are equivalent.
Reusing `RenderedMessage` is preferred because it is already Promptkit's
public, stable representation of a provider-bound chat message and includes
the supported cache-control metadata. Its GoDoc should be generalized from a
definition-supplied message to an already-rendered chat message. A new
request-only message type would duplicate the same role, content, and cache
control contract without adding useful type safety.
The exact exported declarations and semantics, once implemented, belong in
GoDoc. The name `AppendedMessages` is intentional: placement is part of the
contract, whereas names such as `AdditionalMessages` leave ordering
ambiguous.
## Composition Semantics
For `Prepare`, `PrepareExecution`, and `Run`, Promptkit should:
1. perform ordinary prompt selection, input loading, variable processing, and
prompt rendering;
2. preserve every ordinary rendered message after its role has passed the
shared canonical normalization, including content, cache-control value,
order, and bytes, without rerendering or rewriting content;
3. normalize and validate request-supplied roles, then defensively copy and
append `RunRequest.AppendedMessages` in caller-supplied order; and
4. treat the combined sequence as the effective rendered prompt for every
later preparation and execution boundary.
The request-supplied messages always follow all prompt-definition messages.
They cannot prepend, insert, replace, or delete definition messages. This
narrow contract protects a stable prompt prefix for provider-side prompt
caching and keeps prompt definitions as the canonical owner of ordinary
prompt content.
Promptkit must not synthesize separators or normalize message content. Content,
including empty or whitespace-only content, remains byte-for-byte caller data;
whether a provider accepts it is provider policy. The effective session ID,
selected profile, model settings, output contract, structured-output
constraint, and input hashes retain their ordinary resolution rules.
`InspectPrompt` remains definition inspection and therefore does not accept or
report request-supplied appended messages.
## Preparation, Hashing, And Inspection
Appended messages must participate in the same immutable preparation boundary
as ordinary rendered messages:
- `PreparedRun.Messages` contains the complete combined sequence;
- `PreparedExecution.Details().Messages` contains an independent defensive
copy of that sequence;
- `Run` and `RunPrepared` send exactly that sequence to the selected model
client;
- `RenderedPromptHash` covers the effective session ID and complete combined
sequence, including appended-message cache-control metadata;
- `PromptHash` continues to identify only the selected prompt definition and
therefore does not change merely because appended messages differ; and
- input hashes continue to describe only named artifacts.
Two requests with identical prompt selection, inputs, variables, session, and
appended messages should prepare equivalent message sequences and rendered
prompt hashes. Changing any normalized appended role, content, order, or
cache-control value should change the rendered prompt hash. The hash encoding
must frame sessions, messages, and fields unambiguously rather than relying on
separators that could also occur in caller-controlled content.
Prepared execution must freeze appended messages at preparation time. Caller
mutation after `PrepareExecution` returns must not affect its details or later
execution, and mutation of a value returned by `Details` must not affect the
opaque executable snapshot.
## Interaction With Structured-Output Repair
The combined message sequence is the complete original prompt for one
Promptkit operation. If Promptkit's structural validator rejects a generated
candidate and a repair attempt is available, the existing repair workflow
should begin from a fresh copy of that combined sequence and then append its
own candidate and correction messages.
For the Notarius use case, the ordering may therefore be:
1. ordinary configured prompt messages;
2. Notarius's previous assistant response;
3. Notarius's semantic-correction user message;
4. Promptkit's latest structurally defective assistant response, if any; and
5. Promptkit's structural-repair user message, if needed.
This is not a recursive retry policy. Promptkit still owns only its configured
structural-repair budget within the current operation. It does not interpret
the purpose of consumer-supplied messages, invoke application validators, or
start a later Notarius stage attempt.
All initial and repair generation calls must continue through the ordinary
prepared-execution, capacity, client, cancellation, credential, structured
output, usage, and error-adaptation paths.
## Message Validation And Provider Boundaries
Promptkit should validate appended messages before publishing a prepared value
or starting model generation. Validation should be shared by `Prepare`,
`PrepareExecution`, and `Run` and should reject:
- invalid UTF-8 in role or content;
- a role that, after normalization, is not `developer`, `system`, `user`, or
`assistant`; or
- invalid cache-control metadata.
Role normalization is intentionally limited to trimming surrounding whitespace
and converting the result to lowercase. Promptkit performs no aliasing or
semantic role translation. In particular, it must not translate `developer`
to `system` or the reverse. `system` is generally the more portable choice
across heterogeneous OpenAI-compatible backends; `developer` remains supported
for providers and models that implement the newer instruction hierarchy. A
backend or model that rejects an otherwise supported role reports that through
the ordinary provider error path.
The same four-role vocabulary and normalization apply to prompt-definition
messages. This deliberately tightens the existing prompt format, which
currently accepts any nonblank role. `tool` is not supported because
Promptkit's text-only message value cannot express its required `tool_call_id`;
the deprecated `function` role likewise requires a name that the value cannot
represent. Those roles should be considered only as part of a deliberate
future tool-call message design.
Promptkit does not impose appended-message count, per-message size, aggregate
size, estimated-token, or context-window limits, and it must not truncate
caller content. Those limits are backend-, model-, and application-specific.
Provider rejection for context length, role support, empty content, or any
other provider rule should cross the existing generation-error boundary with
its supported status and provider-detail information intact.
Invalid appended messages are invalid requests and should preserve the public
`ErrInvalidRequest` identity. Error text should identify the failing appended
message index and violated property without echoing its role or content. An
invalid role in a prompt definition remains a source-owned prompt-definition
failure through its ordinary public error category.
## Ownership, Privacy, And Formatting
`RunRequest.AppendedMessages` is caller-owned. Promptkit must defensively copy
the slice and nested cache-control values before retaining them. Internal
domain conversion, prepared-run cloning, public result conversion, and
prepared-execution details must preserve the same non-aliasing guarantees as
ordinary rendered messages.
Appended content can contain model output, source excerpts, or application
validation feedback and must be treated as sensitive prompt content:
- `RunRequest.String` and `RunRequest.GoString` should report only an appended
message count, not roles or content;
- errors must not reproduce appended content;
- no credential or secret value may be inferred, copied, or added to hashes
beyond the existing prompt-content behavior;
- prepared values continue to expose complete rendered messages by design and
remain subject to the consumer's data-handling policy; and
- Promptkit should not add logging, persistence, or a durable retry transcript
for appended messages.
The field does not need a stable `RunRequest` JSON contract because
`RunRequest` has none today. The stable JSON representation of prepared values
must continue to include the final `Messages` sequence through its existing
contract.
## Application-Neutral Boundary
This feature is a generic prompt-composition primitive. Promptkit should not
add concepts such as semantic validator, rejection, stage, pipeline,
correction reason code, or terminal validation policy to its public API.
The following remain downstream responsibilities:
- deciding whether a new request is warranted;
- choosing and enforcing the outer retry budget;
- retaining the exact earlier candidate;
- constructing, bounding, and redacting application feedback;
- determining which message roles and sequence express that workflow;
- keeping preparation inputs stable when an identical prefix is required, or
checking prepared hashes before execution;
- aggregating semantic validation findings;
- recording application provenance; and
- deciding whether exhausted retries fail, reject, warn, or continue.
Promptkit owns only safe composition and execution of the effective request.
## Documentation End State
In the target state, the capability is described by the following canonical
owners:
- root GoDoc for `RunRequest`, `RenderedMessage`, `PreparedRun`, and relevant
engine operations, including the role constants and normalization contract;
- the [framework format reference](../formats.md) for the tightened role
vocabulary and normalization shared by prompt-definition messages;
- the [Go consumer guide](../consumers/pkg-promptkit.md) with one concise
appended-message example and a warning that values are already rendered and
potentially sensitive;
- the [internal runner document](../internal/runner.md) for validation,
composition, hashing, preparation, and structural-repair interaction;
- the [internal source document](../internal/sources.md) for
prompt-definition role normalization and source-owned failures;
- the [internal model-client document](../internal/llm.md) only if its
effective-message input contract needs clarification;
- the [architecture policy](../policy/architecture.md) only if implementation
establishes a durable request-composition invariant not already covered by
the public-facade and application-neutral-boundary rules;
- the
[OpenAI-compatible integration contract](../integrations/openai-compatible-chat.md)
for supported provider-bound roles and provider rejection; and
- release guidance for the new minor version, including migration guidance for
consumers with nonstandard prompt-definition roles.
The Go-only appended-message field does not belong in `docs/formats.md`.
However, the prompt-definition role contract does change and must be updated in
that canonical format reference. Profile and schema formats do not change.
## Verification Expectations
Tests protect the public composition and ownership contract rather than
private helper layout or exact diagnostic prose. Focused coverage establishes:
- nil and empty appended slices preserve current behavior;
- ordinary rendered messages remain an exact prefix and appended messages
retain caller order;
- supported roles in both sources normalize by trimming and lowercasing, while
unsupported, `tool`, and `function` roles are rejected at the owning
boundary;
- message content, including empty and whitespace-only content, is preserved
exactly and receives no Promptkit-owned count or size restriction;
- `Prepare`, `PrepareExecution`, and `Run` agree on the combined sequence and
rendered prompt hash;
- `RunPrepared` uses its frozen appended-message snapshot after caller
mutation;
- `Details` and public conversions return independent copies;
- changing a normalized appended role, content, order, or cache control changes
the
rendered prompt hash without changing `PromptHash`;
- invalid appended roles, UTF-8, and cache controls fail as invalid requests
before model generation without echoing sensitive content;
- provider rejection of an otherwise structurally valid combined sequence
retains the ordinary generation-error contract;
- the initial model call receives the combined sequence;
- structural repair reconstructs the complete combined sequence before adding
Promptkit's repair turns;
- usage, capacity, cancellation, credentials, structured output, and error
identity retain their existing behavior; and
- `String` and `GoString` expose only a count for appended messages.
One external-package contract test and focused internal runner tests should
own most of this matrix. Extend the maintained offline run example only if it
materially improves consumer understanding; do not turn the example into a
retry framework or duplicate the contract tests.
## Acceptance Criteria
- A consumer can append already-rendered messages through `RunRequest` without
defining a second prompt manifest or bypassing `Engine`.
- Normalized ordinary prompt messages remain an unchanged, ordered prefix.
- The final sequence is prepared, hashed, reported in prepared details, frozen,
executed, and repaired consistently across all engine workflows.
- Roles are normalized to and validated against the supported four-role
vocabulary at both prompt-definition and appended-message boundaries.
- Appended values are structurally validated, defensively copied, and excluded
from ordinary request formatting and error text without Promptkit-owned
count, size, token, or context-window limits.
- Promptkit structural repair treats the combined sequence as its original
prompt and retains all existing execution safeguards.
- Promptkit introduces no application-specific retry, validation, pipeline, or
terminal-policy concepts.
- Existing consumers that omit appended messages and use supported roles retain
current behavior; nonstandard prompt-definition roles are intentionally
rejected under the tightened minor-version contract.
- Canonical GoDoc and consumer/internal documentation describe the implemented
contract, and the complete maintainer validation workflow passes.
## Non-Goals
- A Promptkit-owned semantic validation framework.
- A generic outer retry orchestrator.
- Automatic interpretation or formatting of downstream validation errors.
- Prepending, inserting, replacing, or deleting configured prompt messages.
- Treating appended content as a template or file reference.
- Accumulating conversation state across `Run` calls.
- Freezing or reusing a base prompt across independent operations.
- A durable session or retry transcript store.
- PromptKit-owned message-count, byte-size, token, or context-window limits.
- Tool calls, tool results, deprecated function messages, or arbitrary custom
message roles.
- Provider-specific branching for Notarius.
- Changes to profile or schema file formats.
## Alternatives Considered
### Paired Initial And Correction Prompt Definitions
A downstream application could register a second prompt definition that
duplicates the initial manifest and adds candidate and correction inputs. This
requires no Promptkit API change, but it duplicates prompt metadata and creates
drift across prompt versions, profile defaults, output contracts, assets, and
hashes. It also makes a generic request-composition need module-specific.
### A Purpose-Specific Repair Request
Promptkit could expose an API accepting `PreviousOutput` and
`ValidationErrors`, mirroring its internal structural repair request. This
would initially fit Notarius, but it would make Promptkit interpret downstream
policy and would conflate application semantic retries with Promptkit
structural repair. A generic append-only message contract is smaller and more
reusable.
### Direct Execution Of A Mutated Prepared Prompt
Promptkit could expose mutable prepared messages or accept a complete caller-
constructed prompt for execution. That would weaken definition ownership,
prepared-execution immutability, hashing, validation, and provenance, and would
make it easy to bypass source and preparation guarantees. The requested API
should compose messages before immutable preparation instead.
### Direct Use Of The Model Client
Notarius could invoke an injected Promptkit-compatible model client directly.
That bypasses the engine's profile resolution, backend admission, structured
output, repair, error mapping, and prepared-execution contracts. It is not an
acceptable downstream integration path.
### A Semantic-Validation Callback
Promptkit could accept an application callback that validates each candidate
and drives another generation inside one `Run`. That could retain one frozen
prompt and aggregate usage automatically, but it would make Promptkit own
callback lifecycle, semantic diagnostics, application retry policy, and the
boundary between typed domain values and raw model output. Keeping semantic
validation and its outer loop downstream leaves the public API smaller and the
library application-neutral.
### A Reusable Prepared Conversation
Promptkit could expose a reusable handle whose frozen base prompt accepts new
turns across multiple executions. That would guarantee a byte-identical prefix
and could support longer-lived chatbot workflows, but it would require a much
larger lifecycle, credential-retention, concurrency, source-freshness, and
history-ownership contract. The stateless append-only request primitive meets
the demonstrated need without precommitting to those policies.
### Complete Caller-Supplied Or Mutable Message Sequences
Promptkit could let a consumer replace, insert, or delete configured messages,
or execute an entirely caller-constructed sequence. That would weaken prompt
definition ownership, hashing, provenance, and the prepared-execution
boundary. Append-only composition provides the needed flexibility while
preserving configured messages as the canonical prefix.

View File

@@ -1,238 +0,0 @@
# External Backend Catalogs
## Status
Accepted for future implementation. This roadmap defines the intended end
state for extracting Promptkit's maintained built-in backend and profile data
into independently versioned Go modules. It describes future behavior only;
the current implementation and canonical documentation remain authoritative
until the extraction is complete.
## Purpose
Move the rapidly changing built-in backend and model-profile catalogs out of
the Promptkit repository without changing how consumers configure or use
Promptkit.
Provider model catalogs change much more frequently than Promptkit's engine,
public API, and internal execution architecture. Independent catalog modules
allow model additions, deprecations, and metadata corrections to be developed,
tested, reviewed, and released without placing that content churn in the core
repository's history. Promptkit can adopt tested catalog releases through
ordinary Go module version updates.
This extraction is an ownership and release-boundary improvement. It does not
eliminate all Promptkit maintenance: Promptkit must still pin and test the
catalog versions that it supplies by default, and adopting a newer default
catalog ordinarily requires a Promptkit dependency update and release.
## Target End State
The OpenRouter and Rakestrawhome catalogs reside in separate, independently
versioned Go repositories and modules. Each module owns exactly one built-in
backend definition and every built-in profile maintained for that backend.
Promptkit imports both modules and assembles their contents as its built-in
catalog. Downstream consumers continue to receive the same built-in backends
and profiles automatically when they construct an engine. They do not need to
import a catalog module, call a registration function, or change existing
configuration.
Promptkit no longer contains duplicate embedded copies of the extracted
profile assets or hard-coded copies of the extracted backend definitions.
Promptkit continues to own:
- the public backend constants and their exact string values;
- catalog decoding, normalization, and validation;
- conversion into Promptkit's internal domain values;
- built-in and consumer-source precedence;
- engine assembly, capacity management, credentials, and execution; and
- the canonical consumer documentation for the built-ins supplied by each
Promptkit release.
The catalog modules supply immutable data. They do not own Promptkit runtime
behavior or become general-purpose provider SDKs.
## External Catalog Modules
Maintain one repository and Go module for OpenRouter and one for the
Rakestrawhome inference backend. Repository and module names make both the
Promptkit relationship and owning backend unambiguous. Every catalog version
selected by Promptkit must be available from the same build environment used
to test and release Promptkit.
Each module should contain:
- a versioned backend manifest containing the backend ID, OpenAI-compatible
base endpoint, API-key environment-variable name, concurrency policy, queue
capacity, and any backend-wide extra parameters;
- the backend's existing profile YAML assets, retaining their current IDs and
behavior;
- a minimal public package that exposes the embedded, read-only catalog as an
`fs.FS` together with the stable root needed to read it;
- focused package documentation describing the asset contract and release
responsibility;
- validation tests appropriate to data that the module can validate without
importing Promptkit; and
- an independent semantic version and release history.
The exported Go surface should remain limited to access to immutable embedded
assets. The modules should use only the Go standard library at runtime. They
must not import Promptkit, duplicate Promptkit's domain types, perform global
registration in `init`, expose mutable registries, read process environment,
or contain credentials.
The backend manifest format should include an explicit schema version.
Promptkit should strictly reject unsupported versions, unknown fields,
malformed values, and files outside the catalog contract. A structured
standard-library format such as JSON is preferred for the backend manifest;
the existing profile YAML format remains unchanged and continues to be parsed
by Promptkit.
## Promptkit Integration
Add an internal catalog adapter that accepts an external catalog's `fs.FS` and
root, decodes its backend manifest, exposes its profile assets through the
existing profile repository boundary, and converts validated data into the
existing internal domain types.
Engine construction should explicitly assemble the imported catalogs. Catalog
packages must not register themselves through package initialization or other
process-global mutable state. The internal backend registry should receive the
assembled built-ins separately from engine-scoped consumer additions so that
the distinction between maintained and consumer-owned IDs remains clear.
The assembled catalog must be validated before use. Validation should cover at
least:
- a supported catalog-manifest schema version;
- all existing backend invariants, including endpoint, environment-variable,
concurrency, queue-capacity, and extra-parameter rules;
- duplicate backend IDs across imported catalogs;
- duplicate profile IDs within or across imported catalogs;
- profile references to the backend owned by their catalog;
- profile-format and inheritance validity under Promptkit's existing rules;
and
- absence of raw API keys or other secret material.
Invalid maintained catalog data should fail engine construction through the
existing configuration-error boundary with enough source context for a
maintainer to identify the catalog, while avoiding content or credential
leakage.
Imported profile repositories remain the lowest-precedence profile source.
Configured, fallback, and in-memory consumer sources retain their existing
precedence and override behavior. Consumer backend registration continues to
accept only new IDs and cannot replace a maintained built-in.
## Compatibility Requirements
The extraction must preserve consumer-visible behavior. In particular:
- `BackendOpenRouter` and `BackendRakestrawHome` retain their current names and
values in the root Promptkit package;
- every current built-in profile retains its ID, model, backend, settings,
output behavior, and inheritance behavior;
- each backend retains its endpoint, API-key environment variable,
concurrency limit, queue capacity, and extra parameters;
- built-ins remain available without additional consumer options;
- consumer-defined backend and profile behavior remains unchanged;
- preparation, execution, errors, capacity, and credential handling remain
Promptkit responsibilities; and
- no new catalog implementation types appear in Promptkit's public API.
Promptkit should record the complete pre-extraction built-in catalog in a
reviewable compatibility fixture or equivalent test-owned snapshot before
switching data sources. Integration tests must prove that the imported
catalogs reproduce it exactly and that source precedence and reserved-ID
behavior remain unchanged.
## Migration Safety Requirements
The migration must not publish a Promptkit state with either zero runtime
owners or two overlaid runtime owners for maintained catalog data. Every
external module version selected in `go.mod` must already be published and
resolvable through ordinary Go module tooling. The frozen pre-extraction
compatibility baseline remains test-owned migration evidence rather than a
runtime fallback.
At cutover, Promptkit must use only the imported modules for runtime catalog
assembly and must remove its embedded profiles and hard-coded backend
definitions in the same accepted source state. That source state must pass the
ordinary and race-enabled test suites, static analysis, builds, maintained
examples, compatibility checks, and repository-hygiene validation. The final
tree has one authoritative runtime data owner for each built-in catalog.
## Catalog Version And Release Policy
Promptkit's `go.mod` should pin catalog versions that have passed Promptkit's
full integration suite. Catalog dependencies should not use local replacement
directives or require a committed Go workspace. The complete module graph and
checksums remain the reproducible identity of a consumer build.
Catalog repositories should adopt an explicit compatibility policy before
their first release:
- adding a new profile is ordinarily backward compatible;
- stable profile IDs should not silently be reassigned to unrelated models;
- metadata corrections should preserve the documented meaning of an ID;
- removals should account for consumer reliance and upstream model
deprecation, with notice or an appropriate catalog version boundary when
practicable; and
- manifest-schema changes must remain compatible with the Promptkit versions
that consume that module version.
Go module version selection may allow an advanced consumer to select a newer
compatible transitive catalog release. Promptkit guarantees only the versions
it pins and tests. This possibility must not require a new Promptkit API or
weaken the catalog compatibility policy.
## Documentation And Release Impact
When the extraction lands, update current-state documentation to reflect that
Promptkit imports rather than embeds its maintained catalog data. The framework
format reference remains the canonical consumer owner of built-in backend and
profile behavior. Internal architecture and component documentation should
describe the external asset boundary without duplicating the catalog's
contents.
The Promptkit release adopting the modules should include a short consumer
release note explaining the new dependency boundary and confirming that no
configuration migration is required. Catalog repository documentation should
link consumers back to Promptkit for runtime behavior and configuration rather
than becoming a parallel Promptkit manual.
## Non-Goals
This feature does not:
- change Promptkit's root public API or require consumers to import catalogs;
- add runtime plugin discovery, network catalog downloads, or dynamic module
loading;
- allow a consumer to replace a maintained backend ID;
- move backend execution, transport, credential, or concurrency logic out of
Promptkit;
- define a general public catalog-authoring SDK;
- automatically select untested catalog releases at runtime;
- guarantee that an upstream provider continues to serve every cataloged
model; or
- extract consumer-defined backends or profiles from their owning
applications.
## Completion Criteria
The feature is complete when:
- both external catalog repositories have released usable, tested modules;
- Promptkit imports and explicitly assembles both catalogs through a private
adapter;
- compatibility tests prove parity with every previously built-in backend and
profile;
- ordinary consumer construction and configuration require no changes;
- consumer overlays and reserved backend IDs retain their behavior;
- Promptkit contains no duplicate embedded catalog assets or hard-coded
definitions for the extracted backends;
- all required Promptkit validation succeeds without a workspace, replacement
directive, real credential, or provider network call; and
- current-state and release documentation accurately describe the resulting
dependency boundary.

View File

@@ -1,661 +0,0 @@
# External Backend Catalogs Implementation Plan
## Purpose
This document is the ordered implementation plan for the target state in
[external-backend-catalogs.md](external-backend-catalogs.md). It is written for
a coding agent implementing one stage per prompt, in order. The feature
roadmap owns the intended end state, compatibility policy, migration safety
requirements, and non-goals; this document owns sequencing and concrete
implementation decisions.
The work spans these three sibling repositories:
- `/home/eric/Workspace/promptkit` with module path
`gitea.maximumdirect.net/eric/promptkit`;
- `/home/eric/Workspace/promptkit-backend-openrouter` with module path
`gitea.maximumdirect.net/eric/promptkit-backend-openrouter`; and
- `/home/eric/Workspace/promptkit-backend-rakestrawhome` with module path
`gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome`.
## Cross-Stage Constraints
Apply these constraints throughout every stage:
- Read and follow each repository's `AGENTS.md` and contributor documentation
before changing it. In Promptkit, always follow
[docs/development.md](../development.md),
[docs/policy/architecture.md](../policy/architecture.md),
[docs/policy/documentation.md](../policy/documentation.md), and
[docs/policy/testing.md](../policy/testing.md).
- Implement exactly one stage per prompt. Start a stage only after every prior
stage's completion criteria are satisfied and committed. Preserve unrelated
work in all three worktrees and commit only the stage's intended files.
- Keep Promptkit an importable Go library. Do not add a command, service,
runtime catalog download, global registration, mutable registry, or public
Promptkit catalog API.
- Keep `BackendOpenRouter` and `BackendRakestrawHome`, their exact values, and
all current consumer construction and source-precedence behavior unchanged.
- Keep the external modules data-only. Their non-test package code may import
only `embed` and `io/fs`; it must not import Promptkit, read environment
variables, perform network calls, or expose mutable assets.
- Use no committed `go.work`, `go.work.sum`, vendor tree, or `replace`
directive. Temporary local workspaces may be used only for exploratory
development and must be disabled for acceptance. Every dependency and
release check must succeed with `GOWORK=off` through ordinary module
resolution.
- Keep all default tests deterministic, offline, and credential-free. Remote
publication and module-resolution checks are release gates, not test-suite
behavior.
- Treat copying the Promptkit-maintained catalog assets into the LGPL-3.0
external repositories as an intentional copyright-holder relicensing
decision. Preserve source provenance and record that decision in both
catalog READMEs; do not imply that an ordinary dependency extraction alone
changes an asset's license.
- Update the canonical current-state documentation in the same commit that
introduces or changes the behavior it describes. In particular, do not
defer an implemented-package inventory or source-boundary update to a later
stage merely because the feature has not reached runtime cutover.
- Treat catalog content and compatibility as high-risk data-integrity work.
Test strict decoding, duplicates, source ownership, immutable copies,
precedence, and exact pre-extraction parity at their narrowest owners; do
not repeat the same malformed-input matrix through the public facade.
- Never include catalog file contents, extra-parameter values, environment
values, or candidate secret values in new diagnostics. Errors may identify
the catalog display name and repository-relative asset path.
- Do not publish or move a tag that already exists. If a planned external
module tag exists locally or remotely, verify that it identifies the exact
intended release commit; otherwise stop and report the conflict.
- Do not create a Promptkit release tag as part of these stages. The external
modules must be released because Promptkit needs resolvable versions, but
Promptkit release publication remains governed by
[docs/release.md](../release.md) after the feature is accepted.
## Shared Catalog Asset Contract
Stages 2 through 6 must use this exact contract in both external modules and
Promptkit's private adapter:
- The module root package is named `openrouter` or `rakestrawhome`, matching
the backend. It exports only:
```go
const Root = "catalog"
func FS() fs.FS
```
`FS` returns the package's embedded filesystem as an `fs.FS`; callers cannot
replace or mutate the embedded value. Both declarations require accurate
GoDoc.
- The embedded tree contains `catalog/backend.json` and one or more profile
files below `catalog/profiles/`. Directories are allowed below `profiles`;
every nondirectory entry there must be a regular `.yml` file. No other file,
symlink, or special entry is part of the embedded tree.
- `backend.json` is one strict JSON object with these required fields and no
others:
```json
{
"schema_version": 1,
"id": "openrouter",
"endpoint": "https://openrouter.ai/api/v1",
"api_key_env": "OPENROUTER_API_KEY",
"concurrency_limit": 16,
"queue_capacity": 1024,
"extra_params": null
}
```
The Rakestrawhome manifest changes `id` to `rakestrawhome`, `endpoint` to
`https://inference.ai.rakestrawhome.com/v1`, `api_key_env` to
`RAKESTRAWHOME_INFERENCE_API_KEY`, and `concurrency_limit` to `4`; it keeps
`schema_version: 1`, `queue_capacity: 1024`, and `extra_params: null`.
`extra_params` may be a JSON object in later compatible catalog releases,
but it remains `null` for the compatibility baseline.
- Schema version `1` requires an integer version, a nonblank ID, endpoint, and
environment-variable name, a positive integer concurrency limit, a
nonnegative integer queue capacity whose sum with the concurrency limit fits
in `int`, and `extra_params` equal to `null` or an object. Promptkit also
applies all existing endpoint, environment-name, reserved-field, and bounded
JSON-value validation.
- OpenRouter owns every existing built-in profile except
`rakestrawhome-gemma-4-31b`; Rakestrawhome owns exactly that profile at the
initial release. Copy the YAML bytes without changing IDs or behavior.
- Each catalog is self-contained: every `base_profile` must resolve inside the
same module. A locally resolved profile must select the manifest's backend
ID. Raw catalog profiles must not provide `endpoint`, `api_key_env`, or a raw
API key; connection and credential-source metadata comes from the manifest.
- Strict source decoding and the bounded JSON-value rules are the primary
secret-safety controls. In addition, recursively reject case-insensitive
catalog `extra_params` keys named `api_key`, `apikey`, `authorization`,
`credential`, `credentials`, `password`, `secret`, `token`, or
`access_token`. `api_key_env` is the sole permitted credential-related
catalog field. Do not use value-pattern heuristics as a substitute for
structural validation; retain Promptkit's repository credential scan as a
separate acceptance check.
- Both external modules start at `v1.0.0`. The two-function/constant asset
surface and manifest schema are intentionally stable; normal profile-data
changes use later semantic versions according to each module's compatibility
policy.
## Stage 1: Freeze The Pre-Extraction Compatibility Baseline
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit`
### Objective
Record the complete current built-in catalog before any data is copied or any
runtime assembly changes. This stage must leave Promptkit using its existing
hard-coded backend definitions and embedded profile repository.
### Implementation
1. Add `testdata/builtin-catalog-v1.json` as a reviewable, permanently frozen
compatibility fixture. Represent both normalized backend definitions and
every raw built-in profile. Include all fields whose zero, empty, nil, or
explicit state affects behavior, including `queue_capacity_set`,
`base_profile`, backend, endpoint, model, execution settings,
`api_key_env`, `api_key_required`, and `extra_params`. Sort backends by ID
and profiles by ID so diffs are deterministic.
2. Add a focused compatibility test under `internal/profile/builtin` that
reads the fixture, obtains normalized built-ins from
`internal/backend.Registry`, discovers every current embedded profile, and
compares complete semantic values. Compare both directions so a missing or
extra backend/profile fails. Reuse the package's existing asset discovery
and profile repository rather than adding production enumeration APIs in
this stage.
3. Do not provide an automatic golden-file update path. The snapshot describes
the migration baseline and must change only by deliberate review of the
fixture itself.
### Tests And Validation
1. Run the focused `internal/backend` and `internal/profile/builtin` tests.
2. Run `go test ./...` and `go test -race ./...`.
3. Inspect the fixture diff against the current YAML and backend constants,
confirm that it contains no credential value, and run the Promptkit
credential scan from the development guide.
### Completion Criteria
- One deterministic fixture accounts for both current backends and every
current raw built-in profile.
- The fixture test fails for added, removed, or semantically changed catalog
data.
- Promptkit runtime construction and production code are unchanged.
## Stage 2: Build And Release The OpenRouter Catalog Module
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit-backend-openrouter`
### Objective
Create the independently testable OpenRouter data module, publish its source
commit, and release immutable tag `v1.0.0` before Promptkit depends on it.
### Implementation
1. Initialize `go.mod` with module path
`gitea.maximumdirect.net/eric/promptkit-backend-openrouter` and Go version
`1.25.5`. Name the root package `openrouter`.
2. Add `catalog/backend.json` using the exact OpenRouter manifest from the
shared contract. Copy every current OpenRouter-owned YAML asset from
`promptkit/internal/profile/builtin/assets/` into
`catalog/profiles/<provider>/` without editing its bytes. Record in the
README that the Promptkit-maintained source assets are intentionally being
distributed under this repository's LGPL-3.0 terms with authorization from
their copyright holder, and identify Promptkit as their source provenance.
3. Add the private embedded filesystem and the exact `Root`/`FS` public
surface. Embed only `catalog`, return the embedded filesystem by value
behind `fs.FS`, and add package GoDoc explaining that the module supplies
immutable Promptkit catalog assets rather than runtime provider behavior.
4. Replace the placeholder README with concise ownership, consumption,
compatibility, and validation guidance. State that Promptkit owns parsing,
runtime behavior, credentials, and consumer documentation; catalog releases
own OpenRouter manifest/profile data. Document the roadmap's ID stability,
additive profile, correction, deprecation/removal, and manifest-schema
compatibility policy.
5. Add `docs/release.md` with a source-only semantic-tag procedure. Require a
clean synchronized `main`, no workspace/replacement/vendor tree, complete
validation, an annotated tag, publication of only the selected tag, remote
tag verification, and resolution from a temporary module with `GOWORK=off`.
6. Add focused tests that use the exported `FS` and `Root` and verify the exact
embedded layout, strict manifest shape and owner ID, at least one profile,
unique trimmed profile IDs, backend selection equal to `openrouter`, no
connection fields or raw API key in profiles, and no forbidden secret key
in nested extra parameters. A test-only `gopkg.in/yaml.v3` dependency is
permitted for robust YAML-node inspection; confirm that the non-test root
package's dependency graph remains standard-library-only.
7. Run `go mod tidy`, commit the module with a short plain-English message,
push `main`, create annotated tag `v1.0.0`, push only that tag, and perform
the documented remote and temporary-module resolution checks. Never use a
local replacement to satisfy the resolution check.
### Tests And Validation
- Run `go test ./...`, `go test -race ./...`, `go vet ./...`, and
`go build ./...` with `GOWORK=off`.
- Require `gofmt -l` to report no tracked Go files and run `git diff --check`
before committing.
- Confirm `GOWORK=off go list -deps .` contains no non-standard-library
runtime package and scan tracked content for credentials.
- After publication, resolve
`gitea.maximumdirect.net/eric/promptkit-backend-openrouter@v1.0.0` from a
temporary module and verify the returned version.
### Completion Criteria
- The OpenRouter module contains its manifest and the complete copied profile
set behind the exact immutable asset API.
- Its validation is offline and its runtime package is standard-library-only.
- `main` and annotated tag `v1.0.0` are published and independently resolvable.
## Stage 3: Build And Release The Rakestrawhome Catalog Module
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit-backend-rakestrawhome`
### Objective
Create and publish the matching Rakestrawhome data module without coupling its
release history or package implementation to the OpenRouter module.
### Implementation
1. Repeat the module, package, immutable asset API, package documentation,
README policy, and `docs/release.md` structure from Stage 2, using module
path `gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome`, package
name `rakestrawhome`, and Go version `1.25.5`.
2. Add the exact Rakestrawhome manifest from the shared contract. Copy only
`google/rakestrawhome-gemma-4-31b.yml` into
`catalog/profiles/google/`, without editing its bytes. Apply the same
explicit LGPL-3.0 relicensing and Promptkit source-provenance statement as
Stage 2.
3. Apply the same focused asset tests, changing the expected backend to
`rakestrawhome` and the initial profile set to the single owned profile.
Keep the module independent: do not import or share code with either
Promptkit or the OpenRouter module.
4. Run the same validation and release sequence as Stage 2, commit and push
`main`, publish annotated tag `v1.0.0`, and verify ordinary module
resolution without a workspace or replacement.
### Tests And Validation
Use the complete Stage 2 validation list with the Rakestrawhome module path.
Inspect the copied profile against Promptkit's Stage 1 fixture before release.
### Completion Criteria
- The Rakestrawhome module contains exactly its owned backend and profile data
behind the same stable asset contract.
- Its tests, build, runtime dependency check, credential scan, and remote
resolution all pass.
- `main` and annotated tag `v1.0.0` are published and independently resolvable.
## Stage 4: Add Eager Immutable Profile Loading To Promptkit
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit`
### Objective
Add the reusable profile-package primitive needed to validate an immutable
external catalog completely at engine construction, without changing the lazy
point-in-time semantics of consumer-configured profile sources.
### Implementation
1. In `internal/profile`, add:
```go
type LoadedProfileMetadata struct {
ID string
Path string
ExplicitFields []string
}
func LoadFSRepository(
ctx context.Context,
fsys fs.FS,
root string,
) (Repository, []LoadedProfileMetadata, error)
```
It must discover sorted YAML paths through `internal/filecatalog`, read each
file once, require exactly one document, strictly decode the existing
profile schema, reject raw API keys, normalize and validate each raw
definition through the existing owners, and reject duplicate trimmed IDs.
Return an immutable in-memory raw repository plus a newly allocated metadata
slice sorted by normalized profile ID. Each metadata entry contains that ID,
the safe root-relative source path, and a newly allocated sorted list of the
exact top-level YAML field names present in the source document. An empty
source returns an empty repository and metadata slice; the catalog adapter,
not this generic primitive, decides whether emptiness is invalid. All three
fields, the type, and the function are internal to the Promptkit module but
require accurate GoDoc because they cross internal package boundaries.
2. Refactor existing private decode/metadata logic only as needed so eager and
point lookup share strict decoding, source-path context, raw-key rejection,
normalization, and defensive JSON-value copying. Do not change
`NewFSRepository`: configured and fallback consumer sources must retain
fresh point-in-time reads and their current error-preserving fallback
semantics. Derive `ExplicitFields` during the same source read and through
the existing YAML-node metadata path; do not make the later catalog adapter
reread or independently parse profile YAML.
3. The returned repository must honor context cancellation before lookup,
return `ErrProfileNotFound` for absence, and publish a fresh profile value
with a deeply copied `ExtraParams` tree on every successful lookup. Do not
pre-resolve `base_profile`; the raw repository must remain suitable for the
existing outer resolving repository and consumer shadowing rules.
4. Update `docs/internal/sources.md` in the same commit to describe the eager
immutable loader as an implemented internal profile boundary. Make clear
that configured consumer sources remain lazy and that engine assembly still
uses the existing embedded built-ins at this stage.
### Tests And Validation
1. Add focused profile-package tests for sorted metadata, exact explicit-field
presence including explicitly empty values, safe relative paths, strict
malformed-input rejection, duplicate IDs, raw API-key rejection, empty
input, cancellation, and defensive copies of metadata and profile values.
Reuse representative existing fixtures and avoid repeating the entire
profile rule matrix already owned by point lookup.
2. Add a parity test showing that eager and ordinary FS repositories publish
the same raw semantic value for representative standalone and derived
profiles.
3. Run `go test ./internal/profile`, `go test -race ./internal/profile`, and
`go test ./...`.
### Completion Criteria
- Promptkit can eagerly load and validate all raw profiles from an `fs.FS`
without creating a second YAML contract implementation.
- Returned metadata and profile values are caller-independent, and the
metadata preserves the distinction between an absent field and an explicitly
empty field.
- Existing configured, fallback, in-memory, and built-in runtime behavior is
unchanged.
- The internal source document accurately distinguishes eager immutable loads
from existing point-in-time consumer source lookup.
## Stage 5: Add And Verify The Private Catalog Adapter
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit`
### Objective
Import the two published module versions and validate them through one private
Promptkit adapter while retaining the existing embedded/hard-coded runtime
source as the active implementation.
### Implementation
1. Add direct requirements at `v1.0.0` for both external module paths and run
`go mod tidy` with `GOWORK=off`. Do not add a replacement or workspace.
2. Add `internal/catalog` with this internal contract:
```go
type Source struct {
Name string
ExpectedBackendID string
FS fs.FS
Root string
}
type Set struct {
Backends []domain.Backend
Profiles profile.Repository
}
func Load(sources ...Source) (Set, error)
```
`Name` is a safe maintainer-facing label used in errors; reject blank or
duplicate names. Require nonnil filesystems, valid non-root asset roots,
nonblank expected IDs, and at least one source. Return caller-independent
backend values and one raw immutable composite profile repository.
3. Strictly enforce the shared asset layout and manifest schema. Use
`json.Decoder.DisallowUnknownFields`, require exactly one JSON value, use
presence-aware raw fields so missing required fields differ from zero or
`null`, reject unsupported schema versions, and check the manifest ID
against `ExpectedBackendID`. When constructing `domain.Backend`, set
`QueueCapacitySet` to `true` because schema version 1 requires an explicit
`queue_capacity`; this ensures normalization preserves later compatible
releases that intentionally select a non-default capacity.
4. Rename the existing private backend normalizer to the internal exported
`backend.NormalizeDefinition` and have both `Registry` and the catalog
adapter call it. This remains inside Go's `internal` boundary and is not a
Promptkit public API. Do not duplicate endpoint, environment-name,
capacity, reserved-field, or bounded JSON-value policy in the adapter.
5. For each source, call `profile.LoadFSRepository` on `<root>/profiles`.
Reject an empty profile set. Use the returned explicit-field metadata to
reject `endpoint` or `api_key_env` whenever the key is present, including
when its YAML value is explicitly empty; do not infer source presence from
the decoded profile's zero values. Validate every raw profile's nested extra
parameters for forbidden secret keys. Resolve every metadata ID through a
source-local `profile.NewResolvingRepository`; this both proves inheritance
is self-contained and verifies that the final backend ID equals the manifest
owner.
6. Reject duplicate backend IDs and duplicate raw profile IDs across sources.
Compose the already validated raw repositories in source order only after
duplicate checks pass. Do not pre-resolve the returned composite: the root
engine must later place consumer sources above it and apply one outer
resolver to preserve base-profile shadowing semantics.
7. Keep errors bounded and redacted. Wrap failures with the safe source name
and relative path where available, but never include raw JSON/YAML values or
extra-parameter content.
8. Add an integration test that imports the released `openrouter` and
`rakestrawhome` packages, supplies their `FS()`/`Root` values with expected
backend IDs, loads the set, and compares it exactly with
`testdata/builtin-catalog-v1.json`. Keep the Stage 1 test against the old
source too; at this point both tests must pass while only the old source
participates in engine construction.
9. Update `docs/policy/architecture.md`, `docs/internal/overview.md`, and
`docs/internal/sources.md` in the same commit. Describe `internal/catalog`
as an implemented private validation/adapter boundary and the external
modules as imported immutable test-verified sources, while stating
accurately that root engine assembly still uses the original built-in
runtime source until cutover.
### Tests And Validation
1. At `internal/catalog`, use `testing/fstest.MapFS` tables for nil/invalid
sources, layout violations, strict/trailing/missing/unsupported manifests,
backend normalization failures, empty profiles, malformed profiles,
cross-source duplicates, missing/cyclic/cross-catalog bases, owner mismatch,
prohibited connection fields, secret-like nested keys, defensive copies,
and redacted source-aware errors.
2. Keep exhaustive YAML rules in `internal/profile` and backend invariants in
`internal/backend`; adapter tests must prove delegation and assembly, not
duplicate those packages' full matrices.
3. Run focused tests for `internal/profile`, `internal/backend`, and
`internal/catalog`, followed by `go test ./...`, `go test -race ./...`,
`go vet ./...`, and `go build ./...`.
4. With `GOWORK=off`, verify `go list -m all` selects exactly `v1.0.0` for both
catalogs and `go mod verify` succeeds.
### Completion Criteria
- Both released external catalogs load through one strict private adapter and
exactly match the frozen baseline.
- Every maintained profile is eagerly validated locally and across the
assembled set before publication.
- Promptkit still executes exclusively from its original built-in data source.
- The current-state architecture and internal inventory describe this
transitional implemented boundary without claiming that cutover is complete.
## Stage 6: Cut Promptkit Over To The External Catalogs
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit`
### Objective
Make the validated external modules Promptkit's sole runtime owners for the
maintained backends and profiles, then remove every duplicate production copy
from Promptkit in the same committed cutover.
### Implementation
1. In root engine assembly, import the two external packages with unambiguous
aliases and construct `catalog.Source` values in deterministic OpenRouter,
Rakestrawhome order. Use safe display names plus
`backend.OpenRouterID`/`backend.RakestrawHomeID` as the expected IDs.
2. Load the maintained `catalog.Set` during `NewEngine` after options and the
required prompt source have been validated, but before constructing the
backend registry, capacity manager, or runner. Map any load failure to
`ErrInvalidConfig` with the prefix `failed to load maintained catalogs` and
preserve redacted catalog/path context. Add no new public error identity.
3. Change `backend.NewRegistry` to accept maintained definitions and consumer
additions as separate slices. Normalize and copy both through the same
path, insert maintained definitions first, and reject every duplicate
across or within the two groups. This preserves the rule that a consumer
cannot replace a maintained ID without retaining hard-coded maintained
definitions inside the registry package.
4. Change `newProfileRepository` to accept the maintained raw repository as
its lowest-precedence source. Preserve this exact overlay order: in-memory,
ordinary configured file/FS/directory, application fallback, maintained
external catalog. Continue to wrap the complete raw overlay in exactly one
`profile.NewResolvingRepository`.
5. Delete `internal/profile/builtin`, including its embedded YAML assets, and
delete `builtInBackends` plus the OpenRouter/Rakestrawhome endpoint,
environment, and concurrency constants from `internal/backend`. Retain the
internal backend ID constants because the root public constants still
alias them, and retain the generic default queue-capacity policy used for
consumer registrations.
6. Move the Stage 1 compatibility assertion to `internal/catalog` and make it
compare only the external loaded set to the frozen fixture. Remove tests
whose sole purpose was the deleted duplicate source; retain or relocate
distinct compatibility, reserved-ID, profile completeness, and native
Rakestrawhome behavior coverage.
7. Update registry and engine tests for explicit maintained definitions. Add
only focused assembled-engine coverage needed to prove that ordinary
construction includes both catalogs, consumer backend IDs cannot replace
either maintained ID, consumer profiles still override catalog profiles,
inherited base lookup still observes the complete precedence chain, and
capacity/credential/endpoint behavior matches the compatibility fixture.
Reuse existing public contract tests wherever they already protect these
outcomes.
8. Confirm with repository search and `go list -deps` that no production Go
file embeds the old assets or hard-codes the extracted endpoints,
environment-variable names, concurrency values, or profile model catalog.
The frozen test fixture and canonical consumer documentation are the only
permitted Promptkit copies of compatibility data.
9. Update `docs/policy/architecture.md`, `docs/internal/overview.md`, and
`docs/internal/sources.md` in the cutover commit: remove the transitional
old-runtime description, remove `internal/profile/builtin`, and describe
explicit root assembly plus eager maintained-catalog validation and the
unchanged outer consumer overlay/resolution boundary.
10. Update `docs/formats.md` where its current wording says the catalog is
embedded or hard-coded. It remains the canonical consumer owner of the
built-ins supplied by a Promptkit release, so retain the exact backend and
profile tables after confirming them against the compatibility fixture.
Do not duplicate pinned module versions there; `go.mod` owns them.
### Tests And Validation
- Run focused backend, catalog, profile, root engine, capacity, and public
contract tests.
- Run the complete Promptkit maintainer workflow from
[docs/development.md](../development.md#maintainer-validation), including
ordinary/race tests, vet, build, both offline examples, Go formatting,
Markdown links, workspace/replacement/vendor guards, whitespace checks,
ignored-file review, credential scan, and full diff/status inspection.
- Run the complete workflow with `GOWORK=off` and no provider credentials.
### Completion Criteria
- Every new engine validates and uses both external catalogs automatically.
- Public constants, effective backend/profile values, source precedence,
inheritance, capacity, credentials, and consumer additions match the frozen
baseline and existing contracts.
- Promptkit has one runtime data owner: no embedded profile catalog or
hard-coded extracted backend definition remains.
## Stage 7: Update Current-State And Release Documentation
**Status:** Complete
### Repository
`/home/eric/Workspace/promptkit`
### Objective
Make durable documentation describe the implemented dependency boundary,
provide concise consumer-facing release guidance, and perform final acceptance
across all three clean repositories.
### Implementation
1. Review the current-state changes made in Stages 4 through 6 against the
final implementation. Correct any stale transitional language and ensure
`docs/policy/architecture.md`, `docs/internal/overview.md`,
`docs/internal/sources.md`, and `docs/formats.md` link to canonical owners
instead of duplicating the manifest or complete catalog.
2. Update `docs/development.md` with a task-specific reading-guide row for
external catalog or maintained built-in changes. Route contributors to the
internal source document, the format reference, testing policy, both module
repositories, and each module's release procedure.
3. Do not add a versionless supplemental release document during feature
implementation. Record for the later Promptkit release-preparation pass that
its versioned `docs/releases/vMAJOR.MINOR.PATCH.md` document should state
that the release adds two independently versioned data dependencies,
preserves the public API and configuration, requires no consumer migration,
and guarantees only the catalog versions selected and tested by that
Promptkit release. That document must link to canonical current-state
documentation rather than restating its contracts.
4. Update each external repository README only if the final implemented paths
or links changed during integration. Do not turn either README into a
parallel Promptkit consumer manual.
5. Mark every stage in this plan complete only after its committed state and
validation evidence exist. Leave Promptkit release tagging to the normal
release procedure; the future annotated Promptkit tag message must include
the dependency-boundary summary and the absence of consumer migration.
### Tests And Validation
1. Run the full Promptkit maintainer workflow, including the local Markdown
link validator, with `GOWORK=off`.
2. In each external module, run its full test/race/vet/build, formatting,
hygiene, credential, and ordinary module-resolution checks.
3. From a temporary module, download both external `v1.0.0` versions and the
current Promptkit commit's module dependencies without a workspace or
replacement. Promptkit itself need not be tagged in this stage.
4. Inspect all three worktrees and their committed diffs. Require each to be
clean and confirm that Promptkit's `go.mod`/`go.sum` identify the published
catalog versions.
### Completion Criteria
- Durable current-state documents accurately describe the external asset
boundary without duplicating implementation-plan detail.
- The later versioned release-document requirements are explicit without
creating a release note before a Promptkit version has been selected.
- All completion criteria in the feature roadmap hold, all three repositories
are clean, and the complete offline validation passes without credentials,
a workspace, a replacement, or provider network access.
## Open Questions
None. The implementation decisions required by this roadmap are fixed above.