From 9e5ed08635cf2867bc639ed97c5e90b8a114ae42 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 14 Oct 2026 01:15:13 +0000 Subject: [PATCH] Propose collection configuration using current ConfigKit --- ...iguration-collections-current-configkit.md | 372 ++++++++++++++++++ docs/roadmap/namespaces.md | 4 + 2 files changed, 376 insertions(+) create mode 100644 docs/roadmap/configuration-collections-current-configkit.md diff --git a/docs/roadmap/configuration-collections-current-configkit.md b/docs/roadmap/configuration-collections-current-configkit.md new file mode 100644 index 0000000..f826bad --- /dev/null +++ b/docs/roadmap/configuration-collections-current-configkit.md @@ -0,0 +1,372 @@ +# Proposal 1: Namespaced Collections With Current ConfigKit + +Status: proposed PromptKit configuration standard; not implemented or accepted. +This proposal addresses the pending configuration decision in the +[namespace and collection roadmap](namespaces.md). It requires PromptKit changes +but no ConfigKit extension. Proposal 2, which may assume upstream changes, is +outside this document. + +## Recommendation + +Keep the existing top-level `backends`, `profiles`, and `prompts` mappings, and +use fully qualified resource IDs as their actual YAML keys. Add a `collections` +mapping keyed by namespace for release metadata. The application's main input +owns `version: 1`; reusable collection contributions omit that top-level field. + +Compose independently maintained peer collections in one ConfigKit underlay +group. Put intentional application/operator overrides in later groups or the +main input. This uses ConfigKit's distinction between strict composition and +explicit precedence to protect independent contributions while retaining +ordinary configuration customization. + +Namespace identity exists in the configuration tree before composition. A +separate `id` field inside an otherwise unqualified prompt definition would be +too late: ConfigKit merges mapping keys before PromptKit interprets definitions. + +## Reviewed Baseline And Evidence + +Reviewed the local `../ConfigKit` checkout at commit +`6fffde4af8f31f99d3ee295c426eb3234cb0fb77` (v0.7.1), the version selected by +PromptKit. The governing upstream sources are its +[configuration contract](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.7.1/docs/config.md) +and [composition/overlay decision](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.7.1/docs/adr/0001-separate-composition-and-overlay.md). +Implementation evidence includes `internal/engine/compose_test.go`, +`internal/engine/load_request_test.go`, `internal/engine/imports_test.go`, and +the public `VersionRule` and `LoadLayered` declarations in that checkout. + +The relevant current rules are: + +| ConfigKit behavior | Consequence for this proposal | +| --- | --- | +| Within a composition group, mappings combine recursively; every other overlapping pair conflicts, even equal scalars | Distinct qualified keys coexist; repeated metadata versions or message sequences conflict within the group | +| Between underlay groups, later values overlay earlier ones; main has higher precedence | Directory order must not accidentally become collection precedence; intentional overrides are separate layers | +| Overlay mappings combine recursively; scalars, sequences, and null replace lower values | An override can replace a setting or message sequence without replacing the entire prompt | +| Syntax and strict-composition failures happen before final domain validation | A higher layer cannot repair a conflicting peer group | +| The configured specification-version path belongs to main; underlays and imported fragments cannot claim it | There is one assembled-configuration specification declaration, not independently checked collection headers | +| Sibling `extends` resolves after composition and precedence | Fully qualified profile keys work as exact cross-collection inheritance references | +| Defaults, typed decoding, resolution, and semantic validation operate on final assembled values | PromptKit need not validate each collection separately | +| Source origins retain resource context and inspection history | File-backed content and explicit provenance inspection can retain existing behavior | + +PromptKit already provides directory and embedded-source helpers, and its +configuration adapter calls `configkit.LoadLayered` once. Its current document +schema only exposes `backends`, `profiles`, and `prompts`; this proposal adds +version and metadata fields rather than claiming they work today. The +[current format reference](../formats.md) remains authoritative until adoption. + +## Proposed Document Shape + +The effective configuration has these application fields. Source documents may +also use supported ConfigKit directives, which ConfigKit consumes before the +application handoff: + +| Field | Proposed meaning | +| --- | --- | +| `version` | PromptKit configuration/collection specification identifier, owned by the main input | +| `collections` | Optional mapping from namespace to collection release metadata | +| `backends` | Existing backend definitions, keyed by exact resource ID | +| `profiles` | Existing profile definitions, keyed by exact resource ID | +| `prompts` | Existing prompt definitions, keyed first by exact resource ID and then by prompt version | + +The namespace roadmap's accepted grammar applies to resource keys: either an +unqualified ID or exactly `namespace/local-name`. Neither qualified component +contains `/`; dots remain available within local names. Published collections +use qualified keys. Unqualified application definitions remain supported. + +Keep IDs in mapping keys, including the prompt's outer key; do not introduce +another embedded `id` or `namespace` field in each definition. Collection +metadata does not prefix or rewrite keys. Filenames and directory names have +no effect on identity. Namespace comparison and references remain exact. + +The following compact contribution could be supplied by a provider collection. +The endpoint and model are synthetic configuration examples, not live targets: + +```yaml +collections: + provider: + version: "2026.10.14" +backends: + provider/default: + endpoint: https://inference.example.invalid/v1 +profiles: + provider/brief: + backend: provider/default + model: vendor/example-model + temperature: 0.2 +``` + +Two independently maintained application collections can then supply the same +local prompt name without colliding: + +```yaml +collections: + weatherreporter: + version: "2.4.0" +prompts: + weatherreporter/alerts.summarize: + "1": + default_profile: provider/brief + messages: + - role: user + content: Summarize the weather alerts. + output: + format: text + validation_mode: none +``` + +```yaml +collections: + homewatch: + version: "3.1.0" +prompts: + homewatch/alerts.summarize: + "1": + default_profile: provider/brief + messages: + - role: user + content: Summarize the household alerts. + output: + format: text + validation_mode: none +``` + +ConfigKit sees separate keys under `prompts`. It never needs to understand +PromptKit namespaces to preserve both definitions. The quoted `"1"` keys are +individual prompt versions, unrelated to the main specification version or the +collection release strings. + +## Specification And Collection Versions + +Configure ConfigKit's existing `VersionRule` at `configkit.Keys("version")`. +Recommend requiring an explicit supported value on the selected main input for +this new standard: canonical YAML is `version: 1`. ConfigKit exposes both integer +`1` and string `"1"` as version text `1`; accept both, while documenting the +integer spelling. Reject absent, null, and unsupported versions through the +configured checker. These are proposed PromptKit format choices, implemented +through ConfigKit's supported callback, not a separate input-scanning pass. + +A minimal main input is: + +```yaml +version: 1 +``` + +Programmatic applications can supply the same main input with `configkit.Mapping`. +Do not infer the specification from a directory name or collection release, strip +version fields from fragments, or silently inject a version to hide a missing +declaration. Requiring main instead of accepting an omitted main is an additional +explicit migration cost of this proposal. + +Reusable collection files, including a collection's root metadata file, omit +top-level `version`. So do operator underlays and imported fragments. ConfigKit +protects the configured version path even if a repeated value would equal `1`. +The version checker is invoked by ConfigKit once, at its normal point before +underlay assembly; it selects the contract for the complete configuration. + +For collection metadata, propose an ordinary optional `collections` mapping: + +- Its keys are exact nonblank namespace names without `/`. +- Each effective entry is a non-null mapping with a required nonblank string + `version`. Quote collection release identifiers; this nested field does not + receive ConfigKit's special integer-to-string handling for top-level `version`. +- Release strings are descriptive publisher identifiers, not executable + dependency constraints or implicit precedence. No SemVer ordering is needed. +- An omitted, null, or empty top-level `collections` mapping means no declared + collection metadata. Null individual entries are invalid final descriptors; + null is not a new per-entry deletion operator. + +Validate this metadata only in final effective values. Do not require a metadata +entry for every resource namespace or require every metadata entry to have +resources. This lets ordinary application configuration add definitions or +overrides without fabricating a collection release. Collection publishing +guidance recommends declaring its own namespace and version, but PromptKit +does not audit which source file originally contributed a resource. + +## Source Selection And Layering + +Recommend this caller-selected assembly: + +| Tier | Contents | Relationship | +| --- | --- | --- | +| First underlay group | All selected peer collection inputs | Strict composition across the entire group | +| Later underlay groups, if selected | Application defaults or operator override documents, in caller-chosen priority order | Strict within each group; later groups overlay earlier groups | +| Main input | Specification declaration and optional highest-priority local settings | Overlays the underlays | + +These are integration guidance, not a new enforced source policy. The application +still selects ConfigKit groups and precedence explicitly. In particular, separate +directories do not each require their own underlay tier. Append their discovered +inputs into one group when they are independent peer libraries. Putting each +directory into a successive group intentionally enables later overrides. + +An illustrative caller arrangement using existing source APIs is: + +```go +var peers []configkit.Input +for _, directory := range collectionDirectories { + inputs, err := promptkit.DirectoryInputs(directory) + if err != nil { return err } + peers = append(peers, inputs...) +} +main := configkit.Input{Source: configkit.File(applicationConfigFile)} +sources := promptkit.ConfigurationSources{ + Underlays: []configkit.Underlay{{Inputs: peers}}, + Main: &main, +} +// Pass sources to NewEngine after the proposed document schema is implemented. +``` + +The helpers already supply filesystem context, recursively select YAML, and +leave imports disabled. An operator may install a collection with an ordinary +`collection.yml` metadata contribution and definition files in subdirectories. +That filename is a recommended packaging convention, not a special loader +instruction. All selected YAML files participate normally; resource files should +be outside that scan or use suitable non-configuration formats. + +For explicit imports instead, select only collection entry-point files, enable +imports on those inputs, and let ConfigKit traverse their fragments. Do not both +recursively select all files and import those same files in the same group: +ConfigKit rejects repeated sources. Imports remain root-contained, relative, +opt-in paths/globs; they neither fetch repositories nor establish precedence. +Each directory input can have its own filesystem root, so peers need not share +an installation directory. + +Importing all peers from main is also possible under ConfigKit's rules, but +main's own definitions and its imports then compose strictly. A main document +cannot override something in its own import graph. The recommended underlay +arrangement keeps main available for ordinary application overrides without +requiring a selected-overlay API addition to PromptKit. + +## Collisions, Partial Contributions, And Overrides + +| Contributions | Current ConfigKit outcome | +| --- | --- | +| `weatherreporter/alerts.summarize` and `homewatch/alerts.summarize` | Both definitions remain | +| Same qualified prompt ID, different prompt-version keys | Both versions remain; PromptKit's existing version selection applies | +| Same ID/version, disjoint definition fields in one group | Fields compose into one definition | +| Same ID/version, overlapping message sequences in one group | `compose.conflict`, even if the sequences are identical | +| Same `collections..version` in two peer inputs | `compose.conflict`, even if release strings are equal | +| Same namespace with disjoint metadata/resource contributions | Contributions can compose; there is no namespace-ownership error | +| Same qualified ID or collection metadata in a later layer | Ordinary recursive overlay semantics apply | + +This does not promise that every repeated resource ID or collection namespace +is fatal. ConfigKit permits disjoint mapping contributions by design. Namespace +qualification avoids accidental *cross-namespace* collisions; strict grouping +detects conflicting leaves among peers. Deliberately sharing an exact key in +different precedence layers still allows overrides. + +For example, main can override only the selected profile's temperature: + +```yaml +version: 1 +profiles: + provider/brief: + temperature: 0.1 +``` + +The lower backend and model remain. Replacing `messages` would replace the +entire sequence, not append messages. An empty mapping does not clear inherited +children. Explicit null retains existing PromptKit field-specific meanings; +this proposal adds no generic delete or replace-whole-resource directive. + +Do not inspect provenance to reject a higher-priority metadata edit, or declare +that a collection namespace is locked to one input. Provenance is for explicit +operator explanation. Final domain validation can reject an invalid resulting +profile or descriptor, but cannot resurrect or reject displaced source values. + +## References, Inheritance, And Resources + +Stored references remain exact IDs. A collection prompt can select +`provider/brief`; a profile can inherit `provider/brief` by writing that exact +key as `extends`. Profile mappings remain siblings under `profiles`, so this +uses ConfigKit's existing inheritance without moving definitions into nested +collection containers. A consumer's selected namespace never rebinds those +stored references. Provider `model` strings remain unchanged and may contain `/`. + +Prompt versions remain an inner mapping. ConfigKit's sibling inheritance is +generic, but it does not make another prompt's outer ID a sibling of an inner +version definition. This proposal introduces no cross-prompt reference resolver. + +Keep existing resource semantics: ConfigKit records the effective field's origin +and source context, and PromptKit uses that context for file-backed prompt +content. Do not derive content paths from namespaces or a collection metadata +filename. Schema roots and runtime artifacts retain their existing separately +configured behavior; a metadata file does not automatically mount schemas or +change their resource resolution. Moving to configuration collections must not +claim broader resource portability than those contracts provide. + +## Collection Inventory + +Propose a public `ListCollections` operation returning detached descriptors from +the engine's captured effective metadata. Exact Go names and signatures remain +to be chosen. Each descriptor contains namespace and declared collection release +version. Expose the effective specification version once at engine/configuration +level, not as a falsely independently checked version on each collection. + +An absent metadata map returns no collection descriptors. Sort by namespace for +stable inspection. Report resources separately through the accepted namespace +listing/filter APIs; unregistered namespaces can exist there without fabricated +collection versions. A metadata-only declaration can appear in collection +inventory even when no resources remain under that namespace. + +This API means "effective declared collections," not "every manifest ever +loaded" or "verified installed package versions." Metadata and resources can +be changed by later layers. A declared release does not certify that all effective +definitions are identical to a publisher's release. ConfigKit inspection explains +the sources and edits; the domain API does not replay history or reread files. + +## PromptKit Changes And Deliberate Limits + +Adoption would add the version field/checker and collection metadata schema, +final-value validation, captured inventory, and the public collection API. It +would also implement the accepted scoped lookup and discovery behavior and +remove automatically bundled catalogs and built-in profiles, as tracked in the +parent roadmap. Update canonical contracts and migration guidance only when +those changes land. Existing documents with no main specification declaration +would need an explicit migration under the proposed version policy. + +There is no ConfigKit change, per-collection loading/validation pass, source-level +namespace registry, key-rewriting stage, or automatic configuration installation. +Definitions retain their qualified keys throughout assembly. Invalid effective +domain values are PromptKit's responsibility; composition, imports, inheritance, +and version-path protection remain ConfigKit's responsibility under +[ADR 0001](../adr/0001-configuration-boundary.md). + +The main limitation is independent specification compatibility: a reusable +collection cannot carry its own protected top-level `version: 1` when imported +or used as an underlay. Publishers/operators must select configuration compatible +with the main standard. Strict decoding catches unsupported surviving fields, +but it is not an independent compatibility check for each collection release. +This is the principal issue to compare with proposal 2. + +## Validation And Review Criteria + +Review verification on 2026-10-14: the focused ConfigKit composition, grouped +loading, and import-version tests passed in the local checkout. A temporary +offline consumer using that checkout also loaded the five YAML examples above +through a prospective document schema and verified distinct identities, equal +and unequal peer conflicts, disjoint contributions, main overrides, atomic +sequence replacement, main/underlay/import version ownership, and qualified +inheritance. This establishes feasibility with current ConfigKit; it does not +implement the proposed PromptKit schema or inventory API. No temporary harness +or module replacement is part of either repository. + +Review this proposal against these observable cases: + +- Two collections using the same local prompt name survive composition under + distinct qualified keys. +- Equal and unequal overlapping metadata versions fail in one peer group; + disjoint contributions compose, and later metadata overrides remain allowed. +- Main overrides one profile field while preserving other fields; sequence + overrides replace whole sequences. +- A strict peer conflict cannot be hidden by a higher layer. +- Main `version: 1` succeeds, while omitted/null/unsupported versions fail the + proposed checker and imported/underlay declarations fail ConfigKit's existing + version ownership checks. +- Fully qualified inheritance resolves through the assembled sibling mappings; + file-backed content retains its declaring source's context. +- Inventory reflects final metadata and remains captured when source files + change; resource namespace discovery does not require a metadata declaration. + +The configuration schema, explicit-version requirement, metadata null rules, +inventory shape, and recommended peer-group layout are proposal choices to +review. They do not resolve the parent roadmap's decision 5 merely by being +written here. diff --git a/docs/roadmap/namespaces.md b/docs/roadmap/namespaces.md index 3886540..015021f 100644 --- a/docs/roadmap/namespaces.md +++ b/docs/roadmap/namespaces.md @@ -485,6 +485,10 @@ dependencies instead. ### 5. Collection Metadata And Specification Versioning (Open) +[Proposal 1: current ConfigKit](configuration-collections-current-configkit.md) +develops a concrete configuration standard for the existing composition rules. +It is a proposal for comparison; this decision remains open. + **Desired capability:** describe collection namespace and release version in configuration and expose an API through which downstream applications can list effective collection declarations captured by an engine. A collection release