From efe885c6b25c8d1311755a902ac74f9b54be6556 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 26 Aug 2026 13:58:22 +0000 Subject: [PATCH] Plan external catalog extraction --- docs/roadmap/external-backend-catalogs.md | 238 ++++++ docs/roadmap/implementation.md | 931 +++++++++++++--------- 2 files changed, 795 insertions(+), 374 deletions(-) create mode 100644 docs/roadmap/external-backend-catalogs.md diff --git a/docs/roadmap/external-backend-catalogs.md b/docs/roadmap/external-backend-catalogs.md new file mode 100644 index 0000000..28d86c5 --- /dev/null +++ b/docs/roadmap/external-backend-catalogs.md @@ -0,0 +1,238 @@ +# 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 index 90f0571..19aaaf4 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,444 +1,627 @@ -# Appended Request Messages Implementation Plan +# External Backend Catalogs Implementation Plan ## Purpose This document is the ordered implementation plan for the target state in -[appended-request-messages.md](appended-request-messages.md). It is written for +[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 intent, policy, acceptance criteria, and non-goals; this document -owns sequencing and concrete implementation decisions. +roadmap owns the intended end state, compatibility policy, migration safety +requirements, and non-goals; this document owns sequencing and concrete +implementation decisions. -The work adds one stateless, append-only request-composition primitive and -tightens prompt-definition roles to the same four-role vocabulary. It does not -add a conversation object, application validation callback, retry policy, -message limit, context estimator, or tool-call representation. +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: -- Preserve the architecture in - [docs/policy/architecture.md](../policy/architecture.md): the root package is - the public facade, `internal/domain` owns source-neutral message invariants, - source packages own source-specific error classification, and - `internal/usecase` owns composition and execution orchestration. -- Keep `RenderedMessage.Role` a string. Do not introduce a new public message - type, enum type, builder, conversation handle, callback, or alternate run - method. -- Support exactly `developer`, `system`, `user`, and `assistant`. Normalize a - role only by trimming surrounding Unicode whitespace with - `strings.TrimSpace` and lowercasing with `strings.ToLower`; do not alias one - role to another. -- Preserve message content exactly after confirming that request-supplied - content is valid UTF-8. Empty and whitespace-only appended content is valid - at the Promptkit boundary. Do not trim, template, load, truncate, estimate, - or otherwise transform it. -- Do not add message-count, byte-size, token, context-window, or retry limits. - An upstream rejection of a structurally valid request must continue through - the existing generation-error boundary. -- Never reproduce appended role text or content in errors, formatting, or new - logging. It is acceptable to identify an invalid message by zero-based index - and property name. -- Defensively copy every retained or returned message slice and every nested - `CacheControl` pointer. Prepared execution must remain an immutable snapshot. -- Keep `PromptHash` definition-only. Treat `RenderedPromptHash` as an opaque - equality value covering the effective session and complete effective - message sequence. -- Follow [docs/policy/testing.md](../policy/testing.md): put exhaustive - invariant matrices at their narrow owner, use external-package tests for the - public contract, avoid repeating the same matrix at every layer, and keep - all tests deterministic and offline. -- Do not create a release tag, push a release, or retire either roadmap during - implementation. Release preparation follows acceptance and review of the - completed feature. +- 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. +- 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. -## Stage 1: Centralize Message Invariants And Tighten Prompt Roles +## Shared Catalog Asset Contract -**Status:** Complete +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:** Pending + +### Repository + +`/home/eric/Workspace/promptkit` ### Objective -Establish one source-neutral owner for chat-message role and cache-control -rules, then make prompt-definition loading and rendering publish only canonical -supported roles. This stage intentionally does not add the public request -field. +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 an `internal/domain` message-invariant file and define the untyped - string constants `RoleDeveloper`, `RoleSystem`, `RoleUser`, and - `RoleAssistant` with the exact values in the feature roadmap. -2. Add a role normalizer in `internal/domain` with the signature and behavior: +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. - ```go - func NormalizeMessageRole(role string) (string, error) - ``` +### Tests And Validation - It must reject invalid UTF-8, trim surrounding whitespace, lowercase the - result, accept exactly the four constants, and reject blank or unsupported - values. Its errors must describe the violated role rule without embedding - the supplied value. -3. Move the source-neutral cache-control normalization currently owned by - `internal/promptdef` into `internal/domain`: - - ```go - func NormalizeCacheControl(control *CacheControl) (*CacheControl, error) - ``` - - Preserve the existing contract: nil remains nil; type and TTL are trimmed; - type must be `ephemeral`; TTL must be empty or `1h`; the returned pointer is - newly allocated. Reject invalid UTF-8 before normalization and do not echo - invalid values. Keep the existing cache-control constants as the canonical - values. -4. Add domain-owned cloning helpers with these signatures: - - ```go - func CloneRenderedMessages(messages []RenderedMessage) []RenderedMessage - func ConcatRenderedMessages(prefix, suffix []RenderedMessage) []RenderedMessage - ``` - - The first returns a deep clone. The second allocates one result in prefix- - then-suffix order and deep-copies both inputs. Both must clone nested - cache-control pointers and preserve role, content, order, nil cache-control, - empty content, and whitespace exactly. Do not normalize or validate inside - a clone helper. -5. Refactor prompt-definition normalization in - `internal/promptdef/filesystem_repository.go` to call the domain role and - cache-control normalizers. Convert the source-only `cacheControlFile` into a - temporary `domain.CacheControl` before normalization; do not move YAML - source types into the domain package. Store the normalized role. Preserve - the existing prompt-definition error category, selected-file context, and - message index, while ensuring diagnostics do not reproduce an invalid role - value. -6. Make `internal/prompt/go_renderer.go` defensively call the same role - normalizer before publishing each rendered message. This protects the - provider-bound invariant even for an internal repository implementation - that did not originate in `internal/promptdef`. Retain the renderer's - existing error identity and message index. Continue to render content and - session templates exactly as before. -7. Replace any literal `"assistant"` and `"user"` roles in - `internal/usecase/repairer.go` with the domain constants. Do not otherwise - change repair behavior in this stage. - -### Tests - -1. Add a table at the domain owner covering: - - - all four accepted roles; - - surrounding whitespace and mixed-case normalization; - - blank, `tool`, `function`, and an arbitrary custom role; - - invalid UTF-8; and - - cache-control normalization, invalid values, and defensive-copy behavior. - -2. Add focused prompt-definition repository coverage proving that a - mixed-case/whitespace role is published canonically and that an unsupported - role is an invalid prompt definition with its existing public-facing error - category. Do not repeat the entire domain role table. -3. Add focused renderer coverage proving defensive canonicalization and - rejection for a directly supplied internal definition. Preserve all - existing rendering and cancellation tests. -4. Run the tests for `internal/domain`, `internal/promptdef`, - `internal/prompt`, and `internal/usecase`, then run `go test ./...` before - completing the stage. +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 -- Every definition-supplied role reaching a rendered prompt is one of the - canonical constants. -- Prompt-definition and renderer failures retain their owning error identities. -- Cache-control rules and message deep-copy behavior have one source-neutral - implementation. -- Existing supported prompt definitions and structural repair behavior remain +- 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:** Pending + +### 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. +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:** Pending + +### 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. +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:** Pending + +### 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 + func LoadFSRepository( + ctx context.Context, + fsys fs.FS, + root string, + ) (Repository, []string, 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 sorted + ID slice. An empty source returns an empty repository and ID slice; the + catalog adapter, not this generic primitive, decides whether emptiness is + invalid. +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. +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 IDs, strict malformed-input + rejection, duplicate IDs, raw API-key rejection, empty input, cancellation, + and defensive copies. 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 IDs and profile values are caller-independent. +- 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 2: Add The Public Request Contract And Boundary Validation +## Stage 5: Add And Verify The Private Catalog Adapter -**Status:** Complete +**Status:** Pending + +### Repository + +`/home/eric/Workspace/promptkit` ### Objective -Expose the smallest public API for appended messages and convert it into a -validated, caller-independent domain request before any source or model work. -The runner may still ignore the new domain field until Stage 3. +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 `AppendedMessages []RenderedMessage` to both the public and domain - `RunRequest` values. Public GoDoc must state that values are already - rendered, are appended after all definition messages, are not templated or - file-resolved, preserve content exactly, and treat nil and empty slices as - equivalent. -2. Add a small root-package file, rather than importing `internal/domain` into - the general public types file solely for constants, and publish untyped - aliases to the domain constants: +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 - const ( - RoleDeveloper = domain.RoleDeveloper - RoleSystem = domain.RoleSystem - RoleUser = domain.RoleUser - RoleAssistant = domain.RoleAssistant - ) + 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) ``` - Give every exported declaration exact GoDoc. Do not introduce a named role - type; assignments and comparisons with ordinary strings must remain - frictionless. -3. Generalize `RenderedMessage` GoDoc so it describes any prepared, - provider-bound text chat message rather than only a definition-supplied - message. Document the four supported roles and refer callers to the - constants. -4. Add one conversion helper in `convert.go` that processes appended messages - in order. For each message it must: + `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`. +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. Validate every raw profile for prohibited + connection/credential fields and forbidden nested secret keys. Resolve + every 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. - - reject invalid UTF-8 in content without reproducing it; - - normalize and validate the role through - `domain.NormalizeMessageRole`; - - normalize and defensively copy cache control through - `domain.NormalizeCacheControl`; and - - return a newly allocated domain slice with independently allocated nested - cache-control pointers. +### Tests And Validation - Wrap failures with the zero-based appended-message index and property name, - but not the failing value. -5. Call that helper from `toDomainRunRequest` and store the result on the - domain request. Because `Prepare`, `PrepareExecution`, and `Run` already use - this shared conversion boundary, conversion failures must emerge as the - existing public `ErrInvalidRequest` before source resolution, preparation, - capacity admission, or model generation. -6. Extend `RunRequest.String` and `RunRequest.GoString` through their shared - redacted formatter to include only `AppendedMessages:`. Do not emit - roles, content, cache-control values, or a serialized message value. - -### Tests - -1. Add an external-package public contract test for the exact constant values - and for ordinary string compatibility. -2. Put the request-message validation matrix at the public conversion owner. - Cover supported normalization, invalid UTF-8 in role and content, - unsupported roles, invalid cache control, empty content, whitespace-only - content, and message-indexed errors that omit the original role and content. -3. Add a small parity test proving that `Prepare`, `PrepareExecution`, and - `Run` all classify an invalid appended message as `ErrInvalidRequest` and do - not invoke configured source/model fakes. Do not repeat the complete - invalid-value matrix through all three operations. -4. Extend the existing request-formatting test so both `String` and `GoString` - expose the count and cannot contain distinctive role/content values. -5. Run root-package tests and `go test ./...` before completing the stage. +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 -- The only new public surface is one `RunRequest` field and four untyped string - constants. -- Every public operation rejects malformed appended messages consistently and - before side effects. -- Request formatting remains redacted. -- Public-to-domain conversion owns an independent, canonical snapshot of the - request values. +- 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 3: Compose, Freeze, Hash, Execute, And Repair The Effective Prompt +## Stage 6: Cut Promptkit Over To The External Catalogs -**Status:** Complete +**Status:** Pending + +### Repository + +`/home/eric/Workspace/promptkit` ### Objective -Make appended messages part of the effective rendered prompt everywhere after -ordinary rendering, including immutable preparation, client execution, -structural repair, and opaque rendered-prompt identity. +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 the shared completion phase in `internal/usecase/runner.go`, append the - already-normalized domain request messages immediately after ordinary - rendering and application of the direct-session override. Use the domain - concatenation helper so the resulting `RenderedPrompt.Messages` owns a deep - copy of both the ordinary prefix and appended suffix. -2. Use that combined rendered prompt for all subsequent work: rendered-prompt - hashing, `PreparedRun.Messages`, `PreparedExecution` details, the initial - generation request, `RunResult.RenderedPromptHash`, and the original-message - input to structural repair. Do not alter `PromptHash` or input hashes. -3. Refactor `clonePreparedRun` and any other prepared-message clone path to use - the shared domain deep-clone helper. Keep public conversion helpers as the - public/domain boundary, but remove duplicate internal cache-control pointer - cloning where the new domain helper is the appropriate owner. -4. Ensure `PreparedExecution` freezes the combined messages. The opaque - retained value, every call to `Details`, and a later `RunPrepared` must have - independent message slices and cache-control pointers. Caller mutation of - the original request or of returned details must not alter execution. -5. In the default structural repairer, start every repair request with a deep - copy of the complete combined original sequence. Append only the latest - nonempty candidate with `domain.RoleAssistant`, followed by the corrective - message with `domain.RoleUser`. Preserve the existing empty-candidate rule, - diagnostic bound, retry budget, usage accounting, capacity path, - cancellation, target, session, credential, and structured-output behavior. -6. Replace the delimiter-based `hashRenderedPrompt` encoding with a versioned, - length-framed binary encoding. Use this exact field order: +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. - 1. the fixed bytes `promptkit/rendered-prompt/v2\x00`; - 2. the effective session ID as a `uint64` big-endian byte length followed by - its bytes; - 3. the message count as `uint64` big-endian; - 4. for each message in order, role and content as separately length-prefixed - byte strings; - 5. one cache-control presence byte; and - 6. when present, type and TTL as separately length-prefixed byte strings. +### Tests And Validation - Hash the resulting framing with SHA-256 and retain the lowercase hexadecimal - public representation. The terminating NUL is part of the fixed marker and - must be written before the first length. Use a fixed eight-byte buffer with - `binary.BigEndian.PutUint64`; do not use `binary.Write`, JSON, `fmt`, - separators, or an input-sized staging buffer. The returned value is opaque, - so do not preserve the old digest. This change deliberately prevents caller - content from colliding with structural separators and streams in constant - auxiliary space. -7. Confirm that the built-in OpenAI-compatible client forwards the canonical - role and exact content without provider-specific translation. No new - provider role branching or proactive context validation is permitted. - -### Tests - -1. At the runner owner, prove that ordinary rendered messages are an exact - prefix, appended messages retain their order and content, and nil/empty - suffixes retain current behavior. -2. Prove that `Prepare`, `PrepareExecution`, and `Run` agree on the complete - sequence and rendered-prompt hash, and that the injected model client sees - exactly that sequence. -3. Prove immutable ownership by mutating the original appended slice and its - cache-control pointer after `PrepareExecution`, then mutating a value from - `Details`; neither mutation may affect later details or `RunPrepared`. -4. Extend hash tests to establish: - - - normalized-equivalent roles yield the same rendered hash; - - changing role, content, order, cache-control presence/value, or session - changes the rendered hash; - - appended-message changes do not change `PromptHash`; and - - the one-message sequence `{role: "user", content: - "x\n---\nassistant\ny"}` hashes differently from the two-message sequence - `{user, "x"}, {assistant, "y"}`, even though the legacy separator - encoding could not distinguish them. - - Test observable relationships, not a golden SHA-256 digest. -5. Extend repair tests to prove the exact order: ordinary prefix, consumer - suffix, latest structurally invalid assistant candidate when nonempty, and - Promptkit corrective user message. Also prove that a later repair begins - again from the complete original combined sequence rather than accumulating - prior repair turns. -6. Add one focused OpenAI-compatible request test covering canonical role - forwarding, including `developer`, and exact content preservation. Do not - duplicate all HTTP client or role-validation tests. -7. Exercise one otherwise valid combined request that the fake provider rejects - and confirm the existing `GenerationError` status/provider-detail contract - remains intact. Reuse the existing generation-error test scaffolding rather - than creating a second status matrix. -8. Run tests for `internal/domain`, `internal/usecase`, `internal/llm`, and the - root package, then run `go test ./...` and `go test -race ./...` before - completing the stage. +- 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 prepared, inspected-prepared, executed, and repaired message sequence - uses the same ordered combined prompt. -- Prepared execution remains immutable across every caller-visible ownership - boundary. -- Rendered-prompt hashing is unambiguous, streaming, and sensitive to every - contracted field while `PromptHash` remains definition-only. -- Existing repair, usage, capacity, cancellation, credential, validation, and - provider-error behavior is preserved. +- 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 4: Publish Canonical Documentation And Complete Validation +## Stage 7: Update Current-State And Release Documentation -**Status:** Complete +**Status:** Pending + +### Repository + +`/home/eric/Workspace/promptkit` ### Objective -Bring every canonical documentation owner into line with the implemented API, -record the intentional prompt-format compatibility change, and run the full -repository validation workflow. +Make durable documentation describe the implemented dependency boundary, +provide concise consumer-facing release guidance, and perform final acceptance +across all three clean repositories. -### Documentation +### Implementation -1. Finish the exact root-package GoDoc for `RunRequest.AppendedMessages`, the - four role constants, `RenderedMessage`, `PreparedRun.Messages`, and the - relevant `Engine` methods. Document normalization, ordering, ownership, - empty-content behavior, error identity, and statelessness at the narrowest - declaration that owns each rule. -2. Update [docs/formats.md](../formats.md) as the canonical prompt-file - contract: +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. Add `docs/releases/external-backend-catalogs.md` as short supplemental + release guidance. State that the adopting Promptkit 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. Link to + canonical current-state documents rather than restating their 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. - - enumerate the four accepted message roles; - - state trim-and-lowercase normalization; - - state that blank, custom, `tool`, and `function` roles are invalid; and - - identify this as an intentional tightening from the prior nonblank-string - rule for consumers migrating to the next minor release. +### Tests And Validation - Do not document the Go-only `AppendedMessages` field as a file-format field. -3. Add one concise, application-neutral example to - [docs/consumers/pkg-promptkit.md](../consumers/pkg-promptkit.md) showing a - previous assistant response and a corrective user message passed through - `AppendedMessages`. Explain that the messages are already rendered and may - contain sensitive data, Promptkit remains stateless, each call re-resolves - current sources, the consumer owns any semantic retry budget, and - `PrepareExecution` plus opaque hash comparison is available when a - pre-execution equality check is required. -4. Update [docs/internal/sources.md](../internal/sources.md) with shared role - normalization and source-owned failure behavior. Update - [docs/internal/runner.md](../internal/runner.md) with append ordering, - immutable preparation, the versioned framed hash, and structural-repair - interaction. Update [docs/internal/llm.md](../internal/llm.md) only if needed - to clarify that the client receives canonical text messages and leaves - supported-role/context rejection to the provider. -5. Update the - [OpenAI-compatible integration contract](../integrations/openai-compatible-chat.md) - to list the four provider-bound roles, state that Promptkit sends them - without translation, and explain that a backend/model rejection follows the - normal provider-error path. Keep tool/function payloads outside the - supported wire contract. -6. Update [docs/policy/architecture.md](../policy/architecture.md) only if the - implementation created a durable architectural invariant not already - covered by domain ownership, the public facade, and the - application-neutral boundary. Do not add feature-level API details to - policy. No new internal package is expected, so do not change the internal - package overview merely because a file was added. -7. Do not create a release tag in this stage. Record in the final handoff that - the next release must be a minor version and that its release note must - summarize both the new append API and migration from nonstandard - prompt-definition roles, linking to GoDoc and `docs/formats.md` for detail. - Create the release document and README link only as part of the repository's - subsequent release-preparation task. - -### Final Verification - -1. Review new tests against [docs/policy/testing.md](../policy/testing.md). - Consolidate repeated setup and assertions only where it clarifies behavior; - do not replace behavioral tests with helper-implementation tests. -2. Run the complete maintainer validation workflow from - [docs/development.md](../development.md), including: - - - all ordinary and race-enabled Go tests; - - `go vet` and the build; - - both maintained examples; - - Go and Markdown formatting/link checks; - - diff, ignored-file, workspace, vendoring, module-replacement, and - credential hygiene checks. - -3. Review the final diff to confirm that no provider-specific Notarius concept, - conversation state, message limit, content logging, new credential handling, - or unrelated refactor entered the change. -4. Leave both roadmap documents in place for post-implementation review. Their - retirement belongs to a later cleanup after the feature is accepted. +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 -- Canonical GoDoc, format, consumer, internal, and integration documentation - agree with the implemented behavior and do not duplicate ownership. -- Migration impact for unsupported legacy prompt roles is explicit. -- The complete maintainer workflow passes from a clean, supported Go setup. -- The repository is ready for an implementation review followed by minor - release preparation. +- Durable current-state documents accurately describe the external asset + boundary without duplicating implementation-plan detail. +- Supplemental release guidance clearly states the compatibility and + dependency impact. +- 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 feature roadmap and this plan resolve the public API shape, role -vocabulary, normalization, content handling, ownership, composition order, -hash framing, repair interaction, provider boundary, documentation ownership, -and release compatibility policy required for implementation. +None. The implementation decisions required by this roadmap are fixed above.