Refine the external catalog implementation plan

This commit is contained in:
2026-08-26 14:20:22 +00:00
parent efe885c6b2
commit b45cea4084

View File

@@ -47,6 +47,11 @@ Apply these constraints throughout every stage:
- Keep all default tests deterministic, offline, and credential-free. Remote - Keep all default tests deterministic, offline, and credential-free. Remote
publication and module-resolution checks are release gates, not test-suite publication and module-resolution checks are release gates, not test-suite
behavior. 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 - Update the canonical current-state documentation in the same commit that
introduces or changes the behavior it describes. In particular, do not introduces or changes the behavior it describes. In particular, do not
defer an implemented-package inventory or source-boundary update to a later defer an implemented-package inventory or source-boundary update to a later
@@ -204,7 +209,10 @@ commit, and release immutable tag `v1.0.0` before Promptkit depends on it.
2. Add `catalog/backend.json` using the exact OpenRouter manifest from the 2. Add `catalog/backend.json` using the exact OpenRouter manifest from the
shared contract. Copy every current OpenRouter-owned YAML asset from shared contract. Copy every current OpenRouter-owned YAML asset from
`promptkit/internal/profile/builtin/assets/` into `promptkit/internal/profile/builtin/assets/` into
`catalog/profiles/<provider>/` without editing its bytes. `catalog/profiles/<provider>/` without editing its bytes. Record in the
README that the Promptkit-maintained source assets are intentionally being
distributed under this repository's LGPL-3.0 terms with authorization from
their copyright holder, and identify Promptkit as their source provenance.
3. Add the private embedded filesystem and the exact `Root`/`FS` public 3. Add the private embedded filesystem and the exact `Root`/`FS` public
surface. Embed only `catalog`, return the embedded filesystem by value surface. Embed only `catalog`, return the embedded filesystem by value
behind `fs.FS`, and add package GoDoc explaining that the module supplies behind `fs.FS`, and add package GoDoc explaining that the module supplies
@@ -271,7 +279,9 @@ release history or package implementation to the OpenRouter module.
name `rakestrawhome`, and Go version `1.25.5`. name `rakestrawhome`, and Go version `1.25.5`.
2. Add the exact Rakestrawhome manifest from the shared contract. Copy only 2. Add the exact Rakestrawhome manifest from the shared contract. Copy only
`google/rakestrawhome-gemma-4-31b.yml` into `google/rakestrawhome-gemma-4-31b.yml` into
`catalog/profiles/google/`, without editing its bytes. `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 3. Apply the same focused asset tests, changing the expected backend to
`rakestrawhome` and the initial profile set to the single owned profile. `rakestrawhome` and the initial profile set to the single owned profile.
Keep the module independent: do not import or share code with either Keep the module independent: do not import or share code with either
@@ -312,27 +322,39 @@ point-in-time semantics of consumer-configured profile sources.
1. In `internal/profile`, add: 1. In `internal/profile`, add:
```go ```go
type LoadedProfileMetadata struct {
ID string
Path string
ExplicitFields []string
}
func LoadFSRepository( func LoadFSRepository(
ctx context.Context, ctx context.Context,
fsys fs.FS, fsys fs.FS,
root string, root string,
) (Repository, []string, error) ) (Repository, []LoadedProfileMetadata, error)
``` ```
It must discover sorted YAML paths through `internal/filecatalog`, read each It must discover sorted YAML paths through `internal/filecatalog`, read each
file once, require exactly one document, strictly decode the existing file once, require exactly one document, strictly decode the existing
profile schema, reject raw API keys, normalize and validate each raw profile schema, reject raw API keys, normalize and validate each raw
definition through the existing owners, and reject duplicate trimmed IDs. definition through the existing owners, and reject duplicate trimmed IDs.
Return an immutable in-memory raw repository plus a newly allocated sorted Return an immutable in-memory raw repository plus a newly allocated metadata
ID slice. An empty source returns an empty repository and ID slice; the slice sorted by normalized profile ID. Each metadata entry contains that ID,
catalog adapter, not this generic primitive, decides whether emptiness is the safe root-relative source path, and a newly allocated sorted list of the
invalid. 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 2. Refactor existing private decode/metadata logic only as needed so eager and
point lookup share strict decoding, source-path context, raw-key rejection, point lookup share strict decoding, source-path context, raw-key rejection,
normalization, and defensive JSON-value copying. Do not change normalization, and defensive JSON-value copying. Do not change
`NewFSRepository`: configured and fallback consumer sources must retain `NewFSRepository`: configured and fallback consumer sources must retain
fresh point-in-time reads and their current error-preserving fallback fresh point-in-time reads and their current error-preserving fallback
semantics. 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, 3. The returned repository must honor context cancellation before lookup,
return `ErrProfileNotFound` for absence, and publish a fresh profile value return `ErrProfileNotFound` for absence, and publish a fresh profile value
with a deeply copied `ExtraParams` tree on every successful lookup. Do not with a deeply copied `ExtraParams` tree on every successful lookup. Do not
@@ -345,10 +367,12 @@ point-in-time semantics of consumer-configured profile sources.
### Tests And Validation ### Tests And Validation
1. Add focused profile-package tests for sorted IDs, strict malformed-input 1. Add focused profile-package tests for sorted metadata, exact explicit-field
rejection, duplicate IDs, raw API-key rejection, empty input, cancellation, presence including explicitly empty values, safe relative paths, strict
and defensive copies. Reuse representative existing fixtures and avoid malformed-input rejection, duplicate IDs, raw API-key rejection, empty
repeating the entire profile rule matrix already owned by point lookup. 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 2. Add a parity test showing that eager and ordinary FS repositories publish
the same raw semantic value for representative standalone and derived the same raw semantic value for representative standalone and derived
profiles. profiles.
@@ -359,7 +383,9 @@ point-in-time semantics of consumer-configured profile sources.
- Promptkit can eagerly load and validate all raw profiles from an `fs.FS` - Promptkit can eagerly load and validate all raw profiles from an `fs.FS`
without creating a second YAML contract implementation. without creating a second YAML contract implementation.
- Returned IDs and profile values are caller-independent. - 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 - Existing configured, fallback, in-memory, and built-in runtime behavior is
unchanged. unchanged.
- The internal source document accurately distinguishes eager immutable loads - The internal source document accurately distinguishes eager immutable loads
@@ -409,18 +435,24 @@ source as the active implementation.
`json.Decoder.DisallowUnknownFields`, require exactly one JSON value, use `json.Decoder.DisallowUnknownFields`, require exactly one JSON value, use
presence-aware raw fields so missing required fields differ from zero or presence-aware raw fields so missing required fields differ from zero or
`null`, reject unsupported schema versions, and check the manifest ID `null`, reject unsupported schema versions, and check the manifest ID
against `ExpectedBackendID`. 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 4. Rename the existing private backend normalizer to the internal exported
`backend.NormalizeDefinition` and have both `Registry` and the catalog `backend.NormalizeDefinition` and have both `Registry` and the catalog
adapter call it. This remains inside Go's `internal` boundary and is not a adapter call it. This remains inside Go's `internal` boundary and is not a
Promptkit public API. Do not duplicate endpoint, environment-name, Promptkit public API. Do not duplicate endpoint, environment-name,
capacity, reserved-field, or bounded JSON-value policy in the adapter. capacity, reserved-field, or bounded JSON-value policy in the adapter.
5. For each source, call `profile.LoadFSRepository` on `<root>/profiles`. 5. For each source, call `profile.LoadFSRepository` on `<root>/profiles`.
Reject an empty profile set. Validate every raw profile for prohibited Reject an empty profile set. Use the returned explicit-field metadata to
connection/credential fields and forbidden nested secret keys. Resolve reject `endpoint` or `api_key_env` whenever the key is present, including
every ID through a source-local `profile.NewResolvingRepository`; this both when its YAML value is explicitly empty; do not infer source presence from
proves inheritance is self-contained and verifies that the final backend ID the decoded profile's zero values. Validate every raw profile's nested extra
equals the manifest owner. 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. 6. Reject duplicate backend IDs and duplicate raw profile IDs across sources.
Compose the already validated raw repositories in source order only after Compose the already validated raw repositories in source order only after
duplicate checks pass. Do not pre-resolve the returned composite: the root duplicate checks pass. Do not pre-resolve the returned composite: the root
@@ -585,12 +617,14 @@ across all three clean repositories.
external catalog or maintained built-in changes. Route contributors to the external catalog or maintained built-in changes. Route contributors to the
internal source document, the format reference, testing policy, both module internal source document, the format reference, testing policy, both module
repositories, and each module's release procedure. repositories, and each module's release procedure.
3. Add `docs/releases/external-backend-catalogs.md` as short supplemental 3. Do not add a versionless supplemental release document during feature
release guidance. State that the adopting Promptkit release adds two implementation. Record for the later Promptkit release-preparation pass that
independently versioned data dependencies, preserves the public API and its versioned `docs/releases/vMAJOR.MINOR.PATCH.md` document should state
configuration, requires no consumer migration, and guarantees only the that the release adds two independently versioned data dependencies,
catalog versions selected and tested by that Promptkit release. Link to preserves the public API and configuration, requires no consumer migration,
canonical current-state documents rather than restating their contracts. 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 4. Update each external repository README only if the final implemented paths
or links changed during integration. Do not turn either README into a or links changed during integration. Do not turn either README into a
parallel Promptkit consumer manual. parallel Promptkit consumer manual.
@@ -616,8 +650,8 @@ across all three clean repositories.
- Durable current-state documents accurately describe the external asset - Durable current-state documents accurately describe the external asset
boundary without duplicating implementation-plan detail. boundary without duplicating implementation-plan detail.
- Supplemental release guidance clearly states the compatibility and - The later versioned release-document requirements are explicit without
dependency impact. creating a release note before a Promptkit version has been selected.
- All completion criteria in the feature roadmap hold, all three repositories - All completion criteria in the feature roadmap hold, all three repositories
are clean, and the complete offline validation passes without credentials, are clean, and the complete offline validation passes without credentials,
a workspace, a replacement, or provider network access. a workspace, a replacement, or provider network access.