Assemble collection resources by qualified ID
This commit is contained in:
@@ -23,13 +23,19 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
||||
"endpoint": "https://example.invalid/v1", "api_key_env": "OPTIONAL_KEY",
|
||||
"extra_params": map[string]any{"token": "private-sentinel", "visible": "public-value"},
|
||||
}},
|
||||
"profiles": map[string]any{
|
||||
"base.v1": map[string]any{"backend": "local", "model": "default-model"},
|
||||
"literal/id": map[string]any{"extends": "base.v1"},
|
||||
},
|
||||
"collections": map[string]any{"literal": map[string]any{
|
||||
"backends": map[string]any{"local": map[string]any{
|
||||
"endpoint": "https://example.invalid/v1",
|
||||
"extra_params": map[string]any{"token": "namespaced-private-sentinel"},
|
||||
}},
|
||||
"profiles": map[string]any{
|
||||
"base.v1": map[string]any{"backend": "local", "model": "default-model"},
|
||||
"id": map[string]any{"extends": "base.v1"},
|
||||
},
|
||||
}},
|
||||
})}}},
|
||||
{Inputs: []configkit.Input{{Source: configkit.Mapping("operator", map[string]any{
|
||||
"profiles": map[string]any{"base.v1": map[string]any{"model": "operator-model"}},
|
||||
"collections": map[string]any{"literal": map[string]any{"profiles": map[string]any{"base.v1": map[string]any{"model": "operator-model"}}}},
|
||||
})}}},
|
||||
},
|
||||
}})
|
||||
@@ -40,17 +46,17 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
||||
if err != nil {
|
||||
t.Fatalf("inspect configuration: %v", err)
|
||||
}
|
||||
modelPath := configkit.Keys("profiles", "base.v1", "model")
|
||||
modelPath := configkit.Keys("collections", "literal", "profiles", "base.v1", "model")
|
||||
model, found := view.Explain(modelPath)
|
||||
if !found || !strings.Contains(model.Effective.YAML, "operator-model") || len(model.History) < 2 {
|
||||
t.Fatalf("layered model explanation = (%+v, %t)", model, found)
|
||||
}
|
||||
inheritedPath := configkit.Keys("profiles", "literal/id", "model")
|
||||
inheritedPath := configkit.Keys("collections", "literal", "profiles", "id", "model")
|
||||
inherited, found := view.Explain(inheritedPath)
|
||||
if !found || !strings.Contains(inherited.Effective.YAML, "operator-model") || len(inherited.History) == 0 || !view.Metadata().IsExplicit(inheritedPath) {
|
||||
t.Fatalf("literal-key inheritance explanation = (%+v, %t)", inherited, found)
|
||||
}
|
||||
if inheritedPath.String() != "/profiles/literal~1id/model" {
|
||||
if inheritedPath.String() != "/collections/literal/profiles/id/model" {
|
||||
t.Fatalf("literal-key path = %q", inheritedPath)
|
||||
}
|
||||
secretPath := configkit.Keys("backends", "local", "extra_params", "token")
|
||||
@@ -58,6 +64,11 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
||||
if !found || !secret.Effective.Redacted || strings.Contains(secret.Effective.YAML, "private-sentinel") {
|
||||
t.Fatalf("credential-shaped provider value was not redacted: (%+v, %t)", secret, found)
|
||||
}
|
||||
namespacedSecretPath := configkit.Keys("collections", "literal", "backends", "local", "extra_params", "token")
|
||||
namespacedSecret, found := view.Explain(namespacedSecretPath)
|
||||
if !found || !namespacedSecret.Effective.Redacted || strings.Contains(namespacedSecret.Effective.YAML, "namespaced-private-sentinel") {
|
||||
t.Fatalf("namespaced provider value was not redacted: (%+v, %t)", namespacedSecret, found)
|
||||
}
|
||||
var effective bytes.Buffer
|
||||
if err := view.WriteEffective(&effective); err != nil {
|
||||
t.Fatalf("write effective configuration: %v", err)
|
||||
@@ -66,7 +77,7 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
||||
t.Fatalf("unexpected effective report: %s", effective.String())
|
||||
}
|
||||
var explanation bytes.Buffer
|
||||
if err := view.WriteExplanation(&explanation, secretPath); err != nil || strings.Contains(explanation.String(), "private-sentinel") {
|
||||
if err := view.WriteExplanation(&explanation, namespacedSecretPath); err != nil || strings.Contains(explanation.String(), "namespaced-private-sentinel") {
|
||||
t.Fatalf("secret explanation = (%q, %v)", explanation.String(), err)
|
||||
}
|
||||
|
||||
|
||||
@@ -508,6 +508,14 @@ release metadata, sibling inheritance, headerless collection fragments, resource
|
||||
named `version` in nested mappings, and header/declaration distinctions.
|
||||
Run `GOWORK=off go test ./internal/configuration .`.
|
||||
|
||||
Completion: final root and collection resources are interpreted from their
|
||||
effective ConfigKit paths and indexed under full runtime IDs without re-keying
|
||||
the retained result. Collection namespaces and local keys are validated;
|
||||
collection release labels, sibling inheritance, inherited relative content,
|
||||
presence/null handling, and namespaced secret redaction are covered. Capacity
|
||||
and profile snapshots use full backend identities. Validation passed:
|
||||
`GOWORK=off go test ./...`, `GOWORK=off go vet ./...`, and `git diff --check`.
|
||||
|
||||
## Stage 9: Make Application Tests Independent Of Bundled Catalogs
|
||||
|
||||
Scope: owned fixtures and examples that currently assume automatic provider data.
|
||||
|
||||
@@ -66,6 +66,11 @@ func secretProviderPath(path configkit.Path) bool {
|
||||
}
|
||||
|
||||
func providerExtraParamsPath(parts []string) bool {
|
||||
return len(parts) >= 4 && parts[1] != "" &&
|
||||
(parts[0] == "backends" || parts[0] == "profiles") && parts[2] == "extra_params"
|
||||
if len(parts) >= 4 && parts[1] != "" &&
|
||||
(parts[0] == "backends" || parts[0] == "profiles") && parts[2] == "extra_params" {
|
||||
return true
|
||||
}
|
||||
return len(parts) >= 6 && parts[0] == "collections" && parts[1] != "" &&
|
||||
(parts[2] == "backends" || parts[2] == "profiles") &&
|
||||
parts[3] != "" && parts[4] == "extra_params"
|
||||
}
|
||||
|
||||
@@ -14,8 +14,11 @@ func TestSecretProviderPathRecognizesResourceExtraParams(t *testing.T) {
|
||||
}{
|
||||
{name: "backend nested secret", path: configkit.Keys("backends", "provider", "extra_params", "nested", "Token"), want: true},
|
||||
{name: "profile nested secret", path: configkit.Keys("profiles", "assistant", "extra_params", "authorization"), want: true},
|
||||
{name: "collection backend nested secret", path: configkit.Keys("collections", "provider", "backends", "default", "extra_params", "nested", "Token"), want: true},
|
||||
{name: "collection profile secret", path: configkit.Keys("collections", "assistant", "profiles", "model", "extra_params", "authorization"), want: true},
|
||||
{name: "unrelated nested mapping", path: configkit.Keys("application", "backends", "provider", "extra_params", "token"), want: false},
|
||||
{name: "extra params outside resource map", path: configkit.Keys("backends", "provider", "options", "extra_params", "token"), want: false},
|
||||
{name: "collection extra params outside resource map", path: configkit.Keys("collections", "provider", "settings", "backends", "default", "extra_params", "token"), want: false},
|
||||
{name: "missing resource key", path: configkit.Keys("backends", "", "extra_params", "token"), want: false},
|
||||
{name: "ordinary provider value", path: configkit.Keys("profiles", "assistant", "extra_params", "provider", "mode"), want: false},
|
||||
}
|
||||
|
||||
@@ -15,16 +15,11 @@ func TestLoadedListsSortedEffectiveIdentities(t *testing.T) {
|
||||
"backends": map[string]any{
|
||||
"local": map[string]any{"endpoint": "http://localhost:8080/v1"},
|
||||
},
|
||||
"profiles": map[string]any{
|
||||
"z/profile": map[string]any{"backend": "local", "model": "z-model"},
|
||||
"partial/profile": map[string]any{"backend": "local"},
|
||||
"a/profile": map[string]any{"backend": "local", "model": "a-model"},
|
||||
" z/profile ": map[string]any{"backend": "local", "model": "spaced-model"},
|
||||
},
|
||||
"prompts": map[string]any{
|
||||
"z/prompt": listingPromptDefinition(),
|
||||
"a/prompt": listingPromptDefinition(),
|
||||
" z/prompt ": listingPromptDefinition(),
|
||||
"collections": map[string]any{
|
||||
"z": map[string]any{"profiles": map[string]any{"profile": map[string]any{"backend": "local", "model": "z-model"}}, "prompts": map[string]any{"prompt": listingPromptDefinition()}},
|
||||
"partial": map[string]any{"profiles": map[string]any{"profile": map[string]any{"backend": "local"}}},
|
||||
"a": map[string]any{"profiles": map[string]any{"profile": map[string]any{"backend": "local", "model": "a-model"}}, "prompts": map[string]any{"prompt": listingPromptDefinition()}},
|
||||
" z": map[string]any{"profiles": map[string]any{"profile ": map[string]any{"backend": "local", "model": "spaced-model"}}, "prompts": map[string]any{"prompt ": listingPromptDefinition()}},
|
||||
},
|
||||
}), nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -17,10 +17,18 @@ import (
|
||||
)
|
||||
|
||||
type document struct {
|
||||
Version *string `yaml:"version"`
|
||||
Backends map[string]backendConfig `yaml:"backends"`
|
||||
Profiles map[string]profileConfig `yaml:"profiles"`
|
||||
Prompts map[string]promptConfig `yaml:"prompts"`
|
||||
Version *string `yaml:"version"`
|
||||
Backends map[string]backendConfig `yaml:"backends"`
|
||||
Profiles map[string]profileConfig `yaml:"profiles"`
|
||||
Prompts map[string]promptConfig `yaml:"prompts"`
|
||||
Collections map[string]collectionConfig `yaml:"collections"`
|
||||
}
|
||||
|
||||
type collectionConfig struct {
|
||||
ReleaseVersion *string `yaml:"release_version"`
|
||||
Backends map[string]backendConfig `yaml:"backends"`
|
||||
Profiles map[string]profileConfig `yaml:"profiles"`
|
||||
Prompts map[string]promptConfig `yaml:"prompts"`
|
||||
}
|
||||
|
||||
type backendConfig struct {
|
||||
@@ -126,6 +134,15 @@ type resourceBases struct {
|
||||
prompts configkit.Path
|
||||
}
|
||||
|
||||
type resourceSet struct {
|
||||
isCollection bool
|
||||
namespace string
|
||||
bases resourceBases
|
||||
backends map[string]backendConfig
|
||||
profiles map[string]profileConfig
|
||||
prompts map[string]promptConfig
|
||||
}
|
||||
|
||||
func rootResourceBases() resourceBases {
|
||||
return resourceBases{
|
||||
backends: configkit.Keys("backends"),
|
||||
@@ -134,6 +151,61 @@ func rootResourceBases() resourceBases {
|
||||
}
|
||||
}
|
||||
|
||||
func resourceSets(config *document) []resourceSet {
|
||||
sets := make([]resourceSet, 0, len(config.Collections)+1)
|
||||
sets = append(sets, resourceSet{
|
||||
bases: rootResourceBases(), backends: config.Backends, profiles: config.Profiles, prompts: config.Prompts,
|
||||
})
|
||||
for _, namespace := range sortedKeys(config.Collections) {
|
||||
collection := config.Collections[namespace]
|
||||
base := configkit.Keys("collections", namespace)
|
||||
sets = append(sets, resourceSet{
|
||||
isCollection: true,
|
||||
namespace: namespace,
|
||||
bases: resourceBases{
|
||||
backends: base.Key("backends"),
|
||||
profiles: base.Key("profiles"),
|
||||
prompts: base.Key("prompts"),
|
||||
},
|
||||
backends: collection.Backends,
|
||||
profiles: collection.Profiles,
|
||||
prompts: collection.Prompts,
|
||||
})
|
||||
}
|
||||
return sets
|
||||
}
|
||||
|
||||
func indexedResourceID(namespace, localKey string) string {
|
||||
qualified, valid := domain.QualifyLocalResourceID(namespace, localKey)
|
||||
if valid {
|
||||
return qualified
|
||||
}
|
||||
if namespace == "" {
|
||||
return localKey
|
||||
}
|
||||
return namespace + "/" + localKey
|
||||
}
|
||||
|
||||
func indexedBackends(config *document) map[string]backendConfig {
|
||||
backends := make(map[string]backendConfig)
|
||||
for _, resources := range resourceSets(config) {
|
||||
for localKey, definition := range resources.backends {
|
||||
backends[indexedResourceID(resources.namespace, localKey)] = definition
|
||||
}
|
||||
}
|
||||
return backends
|
||||
}
|
||||
|
||||
func indexedProfiles(config *document) map[string]profileConfig {
|
||||
profiles := make(map[string]profileConfig)
|
||||
for _, resources := range resourceSets(config) {
|
||||
for localKey, definition := range resources.profiles {
|
||||
profiles[indexedResourceID(resources.namespace, localKey)] = definition
|
||||
}
|
||||
}
|
||||
return profiles
|
||||
}
|
||||
|
||||
var envName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||
|
||||
var backendFields = []string{"endpoint", "api_key_env", "concurrency_limit", "queue_capacity", "extra_params", "smoke_tests"}
|
||||
@@ -146,7 +218,6 @@ func Load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
|
||||
// load performs one ConfigKit lifecycle over the selected source tiers.
|
||||
func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error) {
|
||||
bases := rootResourceBases()
|
||||
state := &loaded{
|
||||
backends: make(map[string]map[string]fieldState),
|
||||
profiles: make(map[string]map[string]fieldState),
|
||||
@@ -162,16 +233,18 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
},
|
||||
Secret: secretProviderPath,
|
||||
Resolve: func(config *document, resolution *configkit.Resolution) error {
|
||||
captureFields(config.Backends, bases.backends, backendFields, resolution, state.backends)
|
||||
captureFields(config.Profiles, bases.profiles, profileFields, resolution, state.profiles)
|
||||
capturePromptMetadata(config.Prompts, bases.prompts, resolution, state.promptMetadata)
|
||||
resolveBackends(config.Backends, bases.backends, resolution)
|
||||
resolveProfiles(config.Profiles, bases.profiles, resolution)
|
||||
resolvePrompts(config.Prompts, bases.prompts, resolution)
|
||||
for _, resources := range resourceSets(config) {
|
||||
captureFields(resources.backends, resources.bases.backends, resources.namespace, backendFields, resolution, state.backends)
|
||||
captureFields(resources.profiles, resources.bases.profiles, resources.namespace, profileFields, resolution, state.profiles)
|
||||
capturePromptMetadata(resources.prompts, resources.bases.prompts, resources.namespace, resolution, state.promptMetadata)
|
||||
resolveBackends(resources.backends, resources.bases.backends, resolution)
|
||||
resolveProfiles(resources.profiles, resources.bases.profiles, resolution)
|
||||
resolvePrompts(resources.prompts, resources.bases.prompts, resolution)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Validate: func(config *document, validation *configkit.Validation) {
|
||||
validateEffective(config, bases, validation)
|
||||
validateEffective(config, validation)
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -179,8 +252,10 @@ func load(main configkit.Input, underlays []configkit.Underlay) (*loaded, error)
|
||||
return nil, fmt.Errorf("load configuration: %w", err)
|
||||
}
|
||||
state.result = result
|
||||
if err := state.materializePrompts(result.Config.Prompts, bases.prompts); err != nil {
|
||||
return nil, fmt.Errorf("load configuration: %w", err)
|
||||
for _, resources := range resourceSets(&result.Config) {
|
||||
if err := state.materializePrompts(resources.prompts, resources.bases.prompts, resources.namespace); err != nil {
|
||||
return nil, fmt.Errorf("load configuration: %w", err)
|
||||
}
|
||||
}
|
||||
if err := state.buildExecutionProfiles(); err != nil {
|
||||
return nil, fmt.Errorf("load configuration: %w", err)
|
||||
@@ -207,7 +282,7 @@ type promptInputPresence struct {
|
||||
description fieldState
|
||||
}
|
||||
|
||||
func capturePromptMetadata(prompts map[string]promptConfig, basePath configkit.Path, resolution *configkit.Resolution, destination map[string][]promptInputPresence) {
|
||||
func capturePromptMetadata(prompts map[string]promptConfig, basePath configkit.Path, namespace string, resolution *configkit.Resolution, destination map[string][]promptInputPresence) {
|
||||
for _, id := range sortedKeys(prompts) {
|
||||
inputs := prompts[id].Inputs
|
||||
states := make([]promptInputPresence, len(inputs))
|
||||
@@ -219,7 +294,7 @@ func capturePromptMetadata(prompts map[string]promptConfig, basePath configkit.P
|
||||
description: captureFieldState(path.Key("description"), resolution),
|
||||
}
|
||||
}
|
||||
destination[id] = states
|
||||
destination[indexedResourceID(namespace, id)] = states
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +310,7 @@ func captureFieldState(path configkit.Path, resolution *configkit.Resolution) fi
|
||||
}
|
||||
}
|
||||
|
||||
func captureFields[T any](entries map[string]T, basePath configkit.Path, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) {
|
||||
func captureFields[T any](entries map[string]T, basePath configkit.Path, namespace string, fields []string, resolution *configkit.Resolution, destination map[string]map[string]fieldState) {
|
||||
for id := range entries {
|
||||
states := make(map[string]fieldState, len(fields))
|
||||
for _, field := range fields {
|
||||
@@ -249,7 +324,7 @@ func captureFields[T any](entries map[string]T, basePath configkit.Path, fields
|
||||
states[field] = fieldValue
|
||||
}
|
||||
}
|
||||
destination[id] = states
|
||||
destination[indexedResourceID(namespace, id)] = states
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,83 +430,111 @@ func resolveProfiles(profiles map[string]profileConfig, basePath configkit.Path,
|
||||
}
|
||||
}
|
||||
|
||||
func validateEffective(config *document, bases resourceBases, validation *configkit.Validation) {
|
||||
for _, id := range sortedKeys(config.Backends) {
|
||||
backend := config.Backends[id]
|
||||
base := bases.backends.Key(id)
|
||||
if strings.TrimSpace(id) == "" {
|
||||
validation.Error(base, "backend ID must not be blank")
|
||||
func validateEffective(config *document, validation *configkit.Validation) {
|
||||
sets := resourceSets(config)
|
||||
backendIDs := make(map[string]struct{})
|
||||
for _, resources := range sets {
|
||||
if resources.isCollection && !domain.IsValidResourceNamespace(resources.namespace) {
|
||||
validation.Error(configkit.Keys("collections", resources.namespace), "collection namespace must be nonblank and contain no slash")
|
||||
}
|
||||
if backend.Endpoint == nil || *backend.Endpoint == "" {
|
||||
validation.Error(base.Key("endpoint"), "endpoint is required")
|
||||
for localKey := range resources.backends {
|
||||
backendIDs[indexedResourceID(resources.namespace, localKey)] = struct{}{}
|
||||
}
|
||||
if backend.APIKeyEnv != nil && *backend.APIKeyEnv != "" && !envName.MatchString(*backend.APIKeyEnv) {
|
||||
validation.Error(base.Key("api_key_env"), "environment variable name is invalid")
|
||||
}
|
||||
if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit < 0 {
|
||||
validation.Error(base.Key("concurrency_limit"), "concurrency limit must not be negative")
|
||||
}
|
||||
if backend.QueueCapacity != nil {
|
||||
if *backend.QueueCapacity < 0 {
|
||||
validation.Error(base.Key("queue_capacity"), "queue capacity must not be negative")
|
||||
}
|
||||
for _, resources := range sets {
|
||||
for _, id := range sortedKeys(resources.backends) {
|
||||
backend := resources.backends[id]
|
||||
base := resources.bases.backends.Key(id)
|
||||
if !domain.IsValidLocalResourceKey(id) {
|
||||
validation.Error(base, "backend key must be a nonblank local key without a slash")
|
||||
}
|
||||
if backend.ConcurrencyLimit == nil || *backend.ConcurrencyLimit <= 0 {
|
||||
validation.Error(base.Key("queue_capacity"), "queue capacity requires a positive concurrency limit")
|
||||
} else if *backend.QueueCapacity > int(^uint(0)>>1)-*backend.ConcurrencyLimit {
|
||||
validation.Error(base.Key("queue_capacity"), "total capacity overflows int")
|
||||
if backend.Endpoint == nil || *backend.Endpoint == "" {
|
||||
validation.Error(base.Key("endpoint"), "endpoint is required")
|
||||
}
|
||||
}
|
||||
validateExtraParams(backend.ExtraParams, base.Key("extra_params"), true, validation)
|
||||
if smokeTests := backend.SmokeTests; smokeTests != nil {
|
||||
smokePath := base.Key("smoke_tests")
|
||||
if smokeTests.DefaultProfile != nil && strings.TrimSpace(*smokeTests.DefaultProfile) == "" {
|
||||
validation.Error(smokePath.Key("default_profile"), "default profile must not be blank")
|
||||
if backend.APIKeyEnv != nil && *backend.APIKeyEnv != "" && !envName.MatchString(*backend.APIKeyEnv) {
|
||||
validation.Error(base.Key("api_key_env"), "environment variable name is invalid")
|
||||
}
|
||||
if authentication := smokeTests.Authentication; authentication != nil {
|
||||
kindPath := smokePath.Key("authentication").Key("kind")
|
||||
if authentication.Kind == nil {
|
||||
validation.Error(kindPath, "authentication kind is required")
|
||||
} else if *authentication.Kind != "none" && *authentication.Kind != "openrouter_key" {
|
||||
validation.Error(kindPath, "authentication kind must be none or openrouter_key")
|
||||
if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit < 0 {
|
||||
validation.Error(base.Key("concurrency_limit"), "concurrency limit must not be negative")
|
||||
}
|
||||
if backend.QueueCapacity != nil {
|
||||
if *backend.QueueCapacity < 0 {
|
||||
validation.Error(base.Key("queue_capacity"), "queue capacity must not be negative")
|
||||
}
|
||||
if backend.ConcurrencyLimit == nil || *backend.ConcurrencyLimit <= 0 {
|
||||
validation.Error(base.Key("queue_capacity"), "queue capacity requires a positive concurrency limit")
|
||||
} else if *backend.QueueCapacity > int(^uint(0)>>1)-*backend.ConcurrencyLimit {
|
||||
validation.Error(base.Key("queue_capacity"), "total capacity overflows int")
|
||||
}
|
||||
}
|
||||
validateExtraParams(backend.ExtraParams, base.Key("extra_params"), true, validation)
|
||||
if smokeTests := backend.SmokeTests; smokeTests != nil {
|
||||
smokePath := base.Key("smoke_tests")
|
||||
if smokeTests.DefaultProfile != nil {
|
||||
if strings.TrimSpace(*smokeTests.DefaultProfile) == "" {
|
||||
validation.Error(smokePath.Key("default_profile"), "default profile must not be blank")
|
||||
} else if !domain.IsValidResourceID(*smokeTests.DefaultProfile) {
|
||||
validation.Error(smokePath.Key("default_profile"), "default profile reference is malformed")
|
||||
}
|
||||
}
|
||||
if authentication := smokeTests.Authentication; authentication != nil {
|
||||
kindPath := smokePath.Key("authentication").Key("kind")
|
||||
if authentication.Kind == nil {
|
||||
validation.Error(kindPath, "authentication kind is required")
|
||||
} else if *authentication.Kind != "none" && *authentication.Kind != "openrouter_key" {
|
||||
validation.Error(kindPath, "authentication kind must be none or openrouter_key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, id := range sortedKeys(config.Profiles) {
|
||||
profile := config.Profiles[id]
|
||||
base := bases.profiles.Key(id)
|
||||
if strings.TrimSpace(id) == "" {
|
||||
validation.Error(base, "profile ID must not be blank")
|
||||
for _, id := range sortedKeys(resources.profiles) {
|
||||
profile := resources.profiles[id]
|
||||
base := resources.bases.profiles.Key(id)
|
||||
if !domain.IsValidLocalResourceKey(id) {
|
||||
validation.Error(base, "profile key must be a nonblank local key without a slash")
|
||||
}
|
||||
if profile.Backend != nil && *profile.Backend != "" {
|
||||
if !domain.IsValidResourceID(*profile.Backend) {
|
||||
validation.Error(base.Key("backend"), "backend reference is malformed")
|
||||
} else if _, exists := backendIDs[*profile.Backend]; !exists {
|
||||
validation.Error(base.Key("backend"), "backend is not defined")
|
||||
}
|
||||
}
|
||||
if profile.Model != nil && *profile.Model != "" && strings.TrimSpace(*profile.Model) == "" {
|
||||
validation.Error(base.Key("model"), "model must not be blank")
|
||||
}
|
||||
if profile.Temperature != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{Temperature: *profile.Temperature}); err != nil {
|
||||
validation.Error(base.Key("temperature"), err.Error())
|
||||
}
|
||||
}
|
||||
if profile.TopP != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{TopP: *profile.TopP}); err != nil {
|
||||
validation.Error(base.Key("top_p"), err.Error())
|
||||
}
|
||||
}
|
||||
if profile.MaxTokens != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{MaxTokens: *profile.MaxTokens}); err != nil {
|
||||
validation.Error(base.Key("max_tokens"), err.Error())
|
||||
}
|
||||
}
|
||||
if profile.TimeoutSeconds != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{TimeoutSeconds: *profile.TimeoutSeconds}); err != nil {
|
||||
validation.Error(base.Key("timeout_seconds"), err.Error())
|
||||
}
|
||||
}
|
||||
validateExtraParams(profile.ExtraParams, base.Key("extra_params"), false, validation)
|
||||
}
|
||||
if profile.Backend != nil && *profile.Backend != "" {
|
||||
if _, exists := config.Backends[*profile.Backend]; !exists {
|
||||
validation.Error(base.Key("backend"), "backend is not defined")
|
||||
for _, id := range sortedKeys(resources.prompts) {
|
||||
prompt := resources.prompts[id]
|
||||
base := resources.bases.prompts.Key(id)
|
||||
if !domain.IsValidLocalResourceKey(id) {
|
||||
validation.Error(base, "prompt key must be a nonblank local key without a slash")
|
||||
}
|
||||
if prompt.DefaultProfile != nil && *prompt.DefaultProfile != "" && !domain.IsValidResourceID(*prompt.DefaultProfile) {
|
||||
validation.Error(base.Key("default_profile"), "default profile reference is malformed")
|
||||
}
|
||||
}
|
||||
if profile.Model != nil && *profile.Model != "" && strings.TrimSpace(*profile.Model) == "" {
|
||||
validation.Error(base.Key("model"), "model must not be blank")
|
||||
}
|
||||
if profile.Temperature != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{Temperature: *profile.Temperature}); err != nil {
|
||||
validation.Error(base.Key("temperature"), err.Error())
|
||||
}
|
||||
}
|
||||
if profile.TopP != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{TopP: *profile.TopP}); err != nil {
|
||||
validation.Error(base.Key("top_p"), err.Error())
|
||||
}
|
||||
}
|
||||
if profile.MaxTokens != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{MaxTokens: *profile.MaxTokens}); err != nil {
|
||||
validation.Error(base.Key("max_tokens"), err.Error())
|
||||
}
|
||||
}
|
||||
if profile.TimeoutSeconds != nil {
|
||||
if err := domain.ValidateExecutionTargetSettings(domain.ExecutionTarget{TimeoutSeconds: *profile.TimeoutSeconds}); err != nil {
|
||||
validation.Error(base.Key("timeout_seconds"), err.Error())
|
||||
}
|
||||
}
|
||||
validateExtraParams(profile.ExtraParams, base.Key("extra_params"), false, validation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,39 +85,55 @@ func TestLoadConsumesAcceptedUnderlayVersionAndValidatesHeaderlessPayload(t *tes
|
||||
|
||||
func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
||||
base := mapping("defaults", map[string]any{
|
||||
"version": int64(1),
|
||||
"backends": map[string]any{
|
||||
"local.v1": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 2},
|
||||
},
|
||||
"profiles": map[string]any{
|
||||
"base/one": map[string]any{"backend": "local.v1", "temperature": 0.8, "top_p": 0.9, "api_key_required": true,
|
||||
"extra_params": map[string]any{"provider": map[string]any{"first": 1, "second": 2}}},
|
||||
"child.v1": map[string]any{"extends": "base/one"},
|
||||
},
|
||||
"prompts": map[string]any{"invoice/summary": map[string]any{
|
||||
"version": "v1.0",
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Summarize."}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||
"collections": map[string]any{"base": map[string]any{
|
||||
"release_version": "",
|
||||
"backends": map[string]any{
|
||||
"local.v1": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 3},
|
||||
},
|
||||
"profiles": map[string]any{
|
||||
"one": map[string]any{"backend": "local.v1", "temperature": 0.8, "top_p": 0.9, "api_key_required": true,
|
||||
"extra_params": map[string]any{"provider": map[string]any{"first": 1, "second": 2}}},
|
||||
"child.v1": map[string]any{"extends": "one"},
|
||||
"namespaced": map[string]any{"backend": "base/local.v1", "model": "collection-model"},
|
||||
},
|
||||
}, "invoice": map[string]any{
|
||||
"release_version": "r7",
|
||||
"prompts": map[string]any{
|
||||
"summary": map[string]any{
|
||||
"version": "v1.0",
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Summarize."}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||
},
|
||||
"version": map[string]any{
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Nested ID."}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
overlay := mapping("operator", map[string]any{
|
||||
"backends": map[string]any{"local.v1": map[string]any{"queue_capacity": 0}},
|
||||
"profiles": map[string]any{"child.v1": map[string]any{
|
||||
"collections": map[string]any{"base": map[string]any{"profiles": map[string]any{"child.v1": map[string]any{
|
||||
"temperature": 0, "top_p": nil, "api_key_required": false,
|
||||
"extra_params": map[string]any{"provider": map[string]any{"second": 3}},
|
||||
}},
|
||||
}}}},
|
||||
})
|
||||
loaded, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{
|
||||
loaded, err := load(mapping("main", map[string]any{"version": "1"}), []configkit.Underlay{
|
||||
{Inputs: []configkit.Input{base}}, {Inputs: []configkit.Input{overlay}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
profile := loaded.result.Config.Profiles["child.v1"]
|
||||
profile := loaded.result.Config.Collections["base"].Profiles["child.v1"]
|
||||
if profile.Temperature == nil || *profile.Temperature != 0 || profile.TopP != nil || profile.APIKeyRequired == nil || *profile.APIKeyRequired {
|
||||
t.Fatalf("effective values were not retained: %+v", profile)
|
||||
}
|
||||
if loaded.profiles["child.v1"]["temperature"] != fieldValue || loaded.profiles["child.v1"]["top_p"] != fieldNull || loaded.profiles["child.v1"]["api_key_required"] != fieldValue {
|
||||
t.Fatalf("effective presence was lost: %+v", loaded.profiles["child.v1"])
|
||||
if loaded.profiles["base/child.v1"]["temperature"] != fieldValue || loaded.profiles["base/child.v1"]["top_p"] != fieldNull || loaded.profiles["base/child.v1"]["api_key_required"] != fieldValue {
|
||||
t.Fatalf("effective presence was lost: %+v", loaded.profiles["base/child.v1"])
|
||||
}
|
||||
provider := profile.ExtraParams["provider"].(map[string]any)
|
||||
if provider["first"] != int64(1) || provider["second"] != int64(3) {
|
||||
@@ -127,9 +143,69 @@ func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
||||
if backend.QueueCapacity == nil || *backend.QueueCapacity != 0 || loaded.backends["local.v1"]["queue_capacity"] != fieldValue {
|
||||
t.Fatalf("explicit zero queue capacity was lost: %+v", backend)
|
||||
}
|
||||
if loaded.result.Config.Prompts["invoice/summary"].Messages[0].Content == nil || loaded.result.Config.Prompts["invoice/summary"].Version == nil || *loaded.result.Config.Prompts["invoice/summary"].Version != "v1.0" {
|
||||
if _, err := loaded.registry.GetBackend("base/local.v1"); err != nil {
|
||||
t.Fatalf("collection backend was not indexed by its full ID: %v", err)
|
||||
}
|
||||
collectionProfile, exists := loaded.indexedProfiles["base/namespaced"]
|
||||
if !exists || collectionProfile.ID != "base/namespaced" || collectionProfile.ResolvedTarget == nil || collectionProfile.ResolvedTarget.BackendID != "base/local.v1" {
|
||||
t.Fatalf("collection profile snapshot = %+v, exists %t", collectionProfile, exists)
|
||||
}
|
||||
if loaded.result.Config.Collections["invoice"].Prompts["summary"].Messages[0].Content == nil || loaded.result.Config.Collections["invoice"].Prompts["summary"].Version == nil || *loaded.result.Config.Collections["invoice"].Prompts["summary"].Version != "v1.0" {
|
||||
t.Fatal("literal prompt ID and version were not decoded")
|
||||
}
|
||||
if loaded.result.Config.Version == nil || *loaded.result.Config.Version != "1" ||
|
||||
loaded.result.Config.Collections["base"].ReleaseVersion == nil || *loaded.result.Config.Collections["base"].ReleaseVersion != "" ||
|
||||
loaded.result.Config.Collections["invoice"].ReleaseVersion == nil || *loaded.result.Config.Collections["invoice"].ReleaseVersion != "r7" {
|
||||
t.Fatalf("root or collection version metadata was not retained: %+v", loaded.result.Config)
|
||||
}
|
||||
if _, _, err := loaded.prompt("invoice/version"); err != nil {
|
||||
t.Fatalf("resource named version was not indexed under its qualified identity: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRejectsInvalidCollectionAndResourceKeys(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
values map[string]any
|
||||
message string
|
||||
}{
|
||||
{
|
||||
name: "namespace with separator",
|
||||
values: map[string]any{"collections": map[string]any{"bad/name": map[string]any{}}},
|
||||
message: "collection namespace must be nonblank and contain no slash",
|
||||
},
|
||||
{
|
||||
name: "root resource with separator",
|
||||
values: map[string]any{"backends": map[string]any{
|
||||
"bad/name": map[string]any{"endpoint": "https://example.test/v1"},
|
||||
}},
|
||||
message: "backend key must be a nonblank local key without a slash",
|
||||
},
|
||||
{
|
||||
name: "collection resource with separator",
|
||||
values: map[string]any{"collections": map[string]any{"good": map[string]any{
|
||||
"prompts": map[string]any{"bad/name": map[string]any{
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Hello."}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||
}},
|
||||
}}},
|
||||
message: "prompt key must be a nonblank local key without a slash",
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, err := load(mapping("invalid names", test.values), nil)
|
||||
var loadErr *configkit.LoadError
|
||||
if err == nil || !errors.As(err, &loadErr) {
|
||||
t.Fatalf("load error = %v, want ConfigKit validation error", err)
|
||||
}
|
||||
for _, diagnostic := range loadErr.Diagnostics() {
|
||||
if diagnostic.Message == test.message {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("load diagnostics = %+v, want message %q", loadErr.Diagnostics(), test.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) {
|
||||
|
||||
@@ -81,8 +81,10 @@ func resolvePrompts(prompts map[string]promptConfig, basePath configkit.Path, re
|
||||
}
|
||||
}
|
||||
|
||||
func (state *loaded) materializePrompts(prompts map[string]promptConfig, basePath configkit.Path) error {
|
||||
state.prompts = make(map[string]*promptEntry, len(prompts))
|
||||
func (state *loaded) materializePrompts(prompts map[string]promptConfig, basePath configkit.Path, namespace string) error {
|
||||
if state.prompts == nil {
|
||||
state.prompts = make(map[string]*promptEntry)
|
||||
}
|
||||
var metadata *configkit.Metadata
|
||||
lookupOrigin := func(path configkit.Path) (configkit.FieldInfo, bool) {
|
||||
if metadata == nil {
|
||||
@@ -96,11 +98,12 @@ func (state *loaded) materializePrompts(prompts map[string]promptConfig, basePat
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return fmt.Errorf("%s: prompt ID must not be blank", promptBase)
|
||||
}
|
||||
entry, err := state.materializePrompt(id, prompts[id].Version, prompts[id], promptBase, lookupOrigin)
|
||||
fullID := indexedResourceID(namespace, id)
|
||||
entry, err := state.materializePrompt(fullID, prompts[id].Version, prompts[id], promptBase, lookupOrigin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.prompts[id] = entry
|
||||
state.prompts[fullID] = entry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
|
||||
func TestLoadCapturesInheritedImportedContent(t *testing.T) {
|
||||
files := fstest.MapFS{
|
||||
"app/main.yaml": {Data: []byte("configkit:\n imports:\n - path: ../catalog/base.yaml\nprompts:\n child:\n extends: base\n")},
|
||||
"catalog/base.yaml": {Data: []byte("prompts:\n base:\n version: \"v1\"\n messages:\n - role: ' USER '\n cache_control:\n type: ' ephemeral '\n ttl: ' 1h '\n content_file: ./body.md\n output:\n format: text\n validation_mode: basic\n")},
|
||||
"app/main.yaml": {Data: []byte("configkit:\n imports:\n - path: ../catalog/base.yaml\ncollections:\n docs:\n prompts:\n child:\n extends: base\n")},
|
||||
"catalog/base.yaml": {Data: []byte("collections:\n docs:\n release_version: r1\n prompts:\n base:\n version: \"v1\"\n messages:\n - role: ' USER '\n cache_control:\n type: ' ephemeral '\n ttl: ' 1h '\n content_file: ./body.md\n output:\n format: text\n validation_mode: basic\n")},
|
||||
"catalog/body.md": {Data: []byte("Captured {{.name}}")},
|
||||
}
|
||||
namespace := configkit.NewFilesystem("embedded", files)
|
||||
@@ -32,7 +32,7 @@ func TestLoadCapturesInheritedImportedContent(t *testing.T) {
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
definition, inspection, err := state.prompt("child")
|
||||
definition, inspection, err := state.prompt("docs/child")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func TestLoadCapturesInheritedImportedContent(t *testing.T) {
|
||||
if message.Role != "user" || message.CacheControl == nil || message.CacheControl.Type != "ephemeral" || message.CacheControl.TTL != "1h" {
|
||||
t.Fatalf("message metadata was not normalized: %+v", message)
|
||||
}
|
||||
field, ok := state.result.Metadata().Lookup(configkit.Keys("prompts", "child", "messages").Index(0).Key("content_file"))
|
||||
field, ok := state.result.Metadata().Lookup(configkit.Keys("collections", "docs", "prompts", "child", "messages").Index(0).Key("content_file"))
|
||||
if !ok || len(field.Origin.Locations) != 1 {
|
||||
t.Fatalf("content origin was lost during normalization: %+v", field)
|
||||
}
|
||||
@@ -57,12 +57,12 @@ func TestLoadCapturesInheritedImportedContent(t *testing.T) {
|
||||
t.Fatalf("content origin = %q, want catalog/base.yaml", originPath)
|
||||
}
|
||||
files["catalog/body.md"].Data = []byte("Changed")
|
||||
again, againInspection, err := state.prompt("child")
|
||||
again, againInspection, err := state.prompt("docs/child")
|
||||
if err != nil || again.Templates[0].Content != "Captured {{.name}}" || againInspection.PromptHash != inspection.PromptHash {
|
||||
t.Fatalf("captured definition changed after source mutation: %+v, %+v, %v", again, againInspection, err)
|
||||
}
|
||||
definition.Templates[0].Content = "caller mutation"
|
||||
third, _, err := state.prompt("child")
|
||||
third, _, err := state.prompt("docs/child")
|
||||
if err != nil || third.Templates[0].Content != "Captured {{.name}}" {
|
||||
t.Fatalf("caller mutation escaped into index: %+v, %v", third, err)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func (state *loaded) buildExecutionProfiles() error {
|
||||
definitions := make(map[string]domain.Backend, len(state.result.Config.Backends))
|
||||
for _, id := range sortedKeys(state.result.Config.Backends) {
|
||||
config := state.result.Config.Backends[id]
|
||||
backendConfigs := indexedBackends(&state.result.Config)
|
||||
profileConfigs := indexedProfiles(&state.result.Config)
|
||||
definitions := make(map[string]domain.Backend, len(backendConfigs))
|
||||
for _, id := range sortedKeys(backendConfigs) {
|
||||
config := backendConfigs[id]
|
||||
definition := domain.Backend{ID: id, ExtraParams: config.ExtraParams}
|
||||
if config.SmokeTests != nil {
|
||||
smokeTests := &domain.BackendSmokeTestConfig{DefaultProfile: config.SmokeTests.DefaultProfile}
|
||||
@@ -49,9 +51,9 @@ func (state *loaded) buildExecutionProfiles() error {
|
||||
return err
|
||||
}
|
||||
state.registry = registry
|
||||
state.indexedProfiles = make(map[string]domain.ExecutionProfile, len(state.result.Config.Profiles))
|
||||
for _, id := range sortedKeys(state.result.Config.Profiles) {
|
||||
config := state.result.Config.Profiles[id]
|
||||
state.indexedProfiles = make(map[string]domain.ExecutionProfile, len(profileConfigs))
|
||||
for _, id := range sortedKeys(profileConfigs) {
|
||||
config := profileConfigs[id]
|
||||
fields := state.profiles[id]
|
||||
target := defaults.ExecutionTargetDefault()
|
||||
if config.Backend != nil && *config.Backend != "" {
|
||||
|
||||
@@ -38,3 +38,27 @@ func resourceNamespaceFromID(id string) (string, bool) {
|
||||
namespace, _, _ := strings.Cut(id, "/")
|
||||
return namespace, true
|
||||
}
|
||||
|
||||
// IsValidResourceID reports whether id is an unqualified resource ID or one
|
||||
// valid namespace/local-ID pair.
|
||||
func IsValidResourceID(id string) bool { return validResourceID(id) }
|
||||
|
||||
// IsValidResourceNamespace reports whether namespace is nonblank and contains
|
||||
// no namespace separator.
|
||||
func IsValidResourceNamespace(namespace string) bool { return validResourceNamespace(namespace) }
|
||||
|
||||
// IsValidLocalResourceKey reports whether key is a nonblank key for a
|
||||
// namespace-local resource mapping.
|
||||
func IsValidLocalResourceKey(key string) bool { return validLocalResourceKey(key) }
|
||||
|
||||
// QualifyLocalResourceID combines a validated namespace and local mapping key.
|
||||
// An empty namespace leaves a valid unqualified key unchanged.
|
||||
func QualifyLocalResourceID(namespace, key string) (string, bool) {
|
||||
if !validLocalResourceKey(key) || (namespace != "" && !validResourceNamespace(namespace)) {
|
||||
return "", false
|
||||
}
|
||||
if namespace == "" {
|
||||
return key, true
|
||||
}
|
||||
return namespace + "/" + key, true
|
||||
}
|
||||
|
||||
@@ -111,23 +111,26 @@ func TestListIdentitiesPreserveContextErrorsWithoutPartialResults(t *testing.T)
|
||||
|
||||
func TestListIdentitiesUseEffectiveLayeredMembership(t *testing.T) {
|
||||
underlay := configkit.Input{Source: configkit.Mapping("consumer defaults", map[string]any{
|
||||
"profiles": map[string]any{
|
||||
"base/profile": map[string]any{"endpoint": "https://example.test/v1", "model": "base-model"},
|
||||
"child/profile": map[string]any{"extends": "base/profile"},
|
||||
"overlay/profile": map[string]any{"endpoint": "https://example.test/v1", "model": "old-model"},
|
||||
},
|
||||
"collections": map[string]any{"shared": map[string]any{
|
||||
"profiles": map[string]any{
|
||||
"base": map[string]any{"endpoint": "https://example.test/v1", "model": "base-model"},
|
||||
"child": map[string]any{"extends": "base"},
|
||||
"overlay": map[string]any{"endpoint": "https://example.test/v1", "model": "old-model"},
|
||||
},
|
||||
"prompts": map[string]any{
|
||||
"prompt": promptDefinitionWithVersion("v1", listingContractPrompt()),
|
||||
},
|
||||
}},
|
||||
"prompts": map[string]any{
|
||||
"shared/prompt": promptDefinitionWithVersion("v1", listingContractPrompt()),
|
||||
"underlay-only": promptDefinitionWithVersion("v1", listingContractPrompt()),
|
||||
},
|
||||
})}
|
||||
main := configkit.Input{Source: configkit.Mapping("application overlay", map[string]any{
|
||||
"profiles": map[string]any{
|
||||
"overlay/profile": map[string]any{"model": "effective-model"},
|
||||
},
|
||||
"collections": map[string]any{"shared": map[string]any{"profiles": map[string]any{
|
||||
"overlay": map[string]any{"model": "effective-model"},
|
||||
}}},
|
||||
"prompts": map[string]any{
|
||||
"shared/prompt": promptDefinitionWithVersion("v2", listingContractPrompt()),
|
||||
"main-only": promptDefinitionWithVersion("v3", listingContractPrompt()),
|
||||
"main-only": promptDefinitionWithVersion("v3", listingContractPrompt()),
|
||||
},
|
||||
})}
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||
@@ -143,24 +146,24 @@ func TestListIdentitiesUseEffectiveLayeredMembership(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, id := range []string{
|
||||
"base/profile", "child/profile", "overlay/profile",
|
||||
"shared/base", "shared/child", "shared/overlay",
|
||||
"promptkit-openrouter-standard", "promptkit-openrouter-auto",
|
||||
} {
|
||||
if !containsListingProfile(profiles, id) {
|
||||
t.Errorf("profile listing %#v does not include %q", profiles, id)
|
||||
}
|
||||
}
|
||||
for _, id := range []string{"base/profile", "child/profile", "overlay/profile"} {
|
||||
for _, id := range []string{"shared/base", "shared/child", "shared/overlay"} {
|
||||
if got := countListingProfile(profiles, id); got != 1 {
|
||||
t.Errorf("profile %q appears %d times, want 1", id, got)
|
||||
}
|
||||
}
|
||||
|
||||
overlay, err := engine.InspectProfile(context.Background(), "overlay/profile")
|
||||
overlay, err := engine.InspectProfile(context.Background(), "shared/overlay")
|
||||
if err != nil || overlay == nil || overlay.EffectiveModelParams.Model != "effective-model" {
|
||||
t.Fatalf("overlay profile did not resolve to its effective definition: (%+v, %v)", overlay, err)
|
||||
}
|
||||
child, err := engine.InspectProfile(context.Background(), "child/profile")
|
||||
child, err := engine.InspectProfile(context.Background(), "shared/child")
|
||||
if err != nil || child == nil || child.EffectiveModelParams.Model != "base-model" {
|
||||
t.Fatalf("inherited child profile did not resolve its effective definition: (%+v, %v)", child, err)
|
||||
}
|
||||
@@ -171,13 +174,13 @@ func TestListIdentitiesUseEffectiveLayeredMembership(t *testing.T) {
|
||||
}
|
||||
wantPrompts := []promptkit.PromptIdentity{
|
||||
{ID: "main-only", Version: stringPointerForTest("v3")},
|
||||
{ID: "shared/prompt", Version: stringPointerForTest("v2")},
|
||||
{ID: "shared/prompt", Version: stringPointerForTest("v1")},
|
||||
{ID: "underlay-only", Version: stringPointerForTest("v1")},
|
||||
}
|
||||
if !reflect.DeepEqual(prompts, wantPrompts) {
|
||||
t.Fatalf("effective prompt identities = %#v, want %#v", prompts, wantPrompts)
|
||||
}
|
||||
if got := countListingPrompt(prompts, "shared/prompt", "v2"); got != 1 {
|
||||
if got := countListingPrompt(prompts, "shared/prompt", "v1"); got != 1 {
|
||||
t.Fatalf("overlaid prompt identity appears %d times, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user