From a4264bf4b420538469f2ab0efb67737d7f71b54f Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 26 Aug 2026 17:31:53 +0000 Subject: [PATCH] Prepare the v0.9.0 release --- README.md | 5 +- docs/releases/v0.9.0.md | 125 ++++ docs/roadmap/appended-request-messages.md | 434 -------------- docs/roadmap/external-backend-catalogs.md | 238 -------- docs/roadmap/implementation.md | 661 ---------------------- 5 files changed, 129 insertions(+), 1334 deletions(-) create mode 100644 docs/releases/v0.9.0.md delete mode 100644 docs/roadmap/appended-request-messages.md delete mode 100644 docs/roadmap/external-backend-catalogs.md delete mode 100644 docs/roadmap/implementation.md diff --git a/README.md b/README.md index 1a7164e..7b2d1e1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/releases/v0.9.0.md b/docs/releases/v0.9.0.md new file mode 100644 index 0000000..e2c2bf8 --- /dev/null +++ b/docs/releases/v0.9.0.md @@ -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. diff --git a/docs/roadmap/appended-request-messages.md b/docs/roadmap/appended-request-messages.md deleted file mode 100644 index f04343d..0000000 --- a/docs/roadmap/appended-request-messages.md +++ /dev/null @@ -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. diff --git a/docs/roadmap/external-backend-catalogs.md b/docs/roadmap/external-backend-catalogs.md deleted file mode 100644 index 28d86c5..0000000 --- a/docs/roadmap/external-backend-catalogs.md +++ /dev/null @@ -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. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md deleted file mode 100644 index ab3b80e..0000000 --- a/docs/roadmap/implementation.md +++ /dev/null @@ -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//` 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 `/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.