Preserve inspection redaction for resource lists
This commit is contained in:
@@ -21,7 +21,7 @@ func TestConfigurationInspectionDiffsCapturedValues(t *testing.T) {
|
|||||||
t.Fatalf("difference count = %d, want 1: %+v", len(differences), differences)
|
t.Fatalf("difference count = %d, want 1: %+v", len(differences), differences)
|
||||||
}
|
}
|
||||||
difference := differences[0]
|
difference := differences[0]
|
||||||
if got, want := difference.Path.String(), "/profiles/offline/model"; got != want {
|
if got, want := difference.Path.String(), "/profiles"; got != want {
|
||||||
t.Fatalf("difference path = %q, want %q", got, want)
|
t.Fatalf("difference path = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
if !strings.Contains(difference.Left.YAML, "earlier-model") || !strings.Contains(difference.Right.YAML, "later-model") {
|
if !strings.Contains(difference.Left.YAML, "earlier-model") || !strings.Contains(difference.Right.YAML, "later-model") {
|
||||||
@@ -94,18 +94,14 @@ func TestConfigurationInspectionDiffReportsPresenceAndSequences(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("compare presence and sequence values: %v", err)
|
t.Fatalf("compare presence and sequence values: %v", err)
|
||||||
}
|
}
|
||||||
if len(differences) != 2 {
|
if len(differences) != 1 || differences[0].Path.String() != "/backends" {
|
||||||
t.Fatalf("difference count = %d, want 2: %+v", len(differences), differences)
|
t.Fatalf("atomic backend list difference = %+v", differences)
|
||||||
}
|
}
|
||||||
byPath := differencesByPath(differences)
|
difference := differences[0]
|
||||||
nullable, ok := byPath["/backends/local/extra_params/nullable"]
|
if !difference.Left.Present || !difference.Right.Present ||
|
||||||
if !ok || nullable.Left.Present || !nullable.Right.Present || strings.TrimSpace(nullable.Right.YAML) != "null" {
|
strings.Contains(difference.Left.YAML, "nullable:") || !strings.Contains(difference.Right.YAML, "nullable: null") ||
|
||||||
t.Fatalf("absent-to-null difference = (%+v, %t)", nullable, ok)
|
!strings.Contains(difference.Left.YAML, "earlier") || !strings.Contains(difference.Right.YAML, "later") {
|
||||||
}
|
t.Fatalf("atomic sequence snapshots = %+v", difference)
|
||||||
sequence, ok := byPath["/backends/local/extra_params/sequence"]
|
|
||||||
if !ok || !sequence.Left.Present || !sequence.Right.Present ||
|
|
||||||
!strings.Contains(sequence.Left.YAML, "earlier") || !strings.Contains(sequence.Right.YAML, "later") {
|
|
||||||
t.Fatalf("atomic sequence difference = (%+v, %t)", sequence, ok)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,22 +119,19 @@ func TestConfigurationInspectionDiffProtectsSensitiveValuesAndReturnedRecords(t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("compare sensitive values: %v", err)
|
t.Fatalf("compare sensitive values: %v", err)
|
||||||
}
|
}
|
||||||
if len(differences) != 2 {
|
if len(differences) != 1 || differences[0].Path.String() != "/backends" {
|
||||||
t.Fatalf("difference count = %d, want 2: %+v", len(differences), differences)
|
t.Fatalf("sensitive backend list difference = %+v", differences)
|
||||||
}
|
}
|
||||||
byPath := differencesByPath(differences)
|
secret := differences[0]
|
||||||
secret, ok := byPath["/backends/local/extra_params/token"]
|
if !secret.Left.Redacted || !secret.Right.Redacted || !secret.HiddenChanged {
|
||||||
if !ok || !secret.Left.Redacted || !secret.Right.Redacted || !secret.HiddenChanged {
|
t.Fatalf("sensitive difference = %+v", secret)
|
||||||
t.Fatalf("sensitive difference = (%+v, %t)", secret, ok)
|
|
||||||
}
|
}
|
||||||
if strings.Contains(secret.Left.YAML, "left-secret-sentinel") || strings.Contains(secret.Left.YAML, "right-secret-sentinel") ||
|
if strings.Contains(secret.Left.YAML, "left-secret-sentinel") || strings.Contains(secret.Left.YAML, "right-secret-sentinel") ||
|
||||||
strings.Contains(secret.Right.YAML, "left-secret-sentinel") || strings.Contains(secret.Right.YAML, "right-secret-sentinel") {
|
strings.Contains(secret.Right.YAML, "left-secret-sentinel") || strings.Contains(secret.Right.YAML, "right-secret-sentinel") {
|
||||||
t.Fatalf("sensitive snapshots disclosed a value: %+v", secret)
|
t.Fatalf("sensitive snapshots disclosed a value: %+v", secret)
|
||||||
}
|
}
|
||||||
visible, ok := byPath["/backends/local/extra_params/visible"]
|
if !strings.Contains(secret.Left.YAML, "before-value") || !strings.Contains(secret.Right.YAML, "after-value") {
|
||||||
if !ok || visible.Left.Redacted || visible.Right.Redacted || visible.HiddenChanged ||
|
t.Fatalf("public sibling missing from snapshots: %+v", secret)
|
||||||
!strings.Contains(visible.Left.YAML, "before-value") || !strings.Contains(visible.Right.YAML, "after-value") {
|
|
||||||
t.Fatalf("public sibling difference = (%+v, %t)", visible, ok)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
differences[0].Path = configkit.Keys("caller", "mutation")
|
differences[0].Path = configkit.Keys("caller", "mutation")
|
||||||
@@ -148,18 +141,15 @@ func TestConfigurationInspectionDiffProtectsSensitiveValuesAndReturnedRecords(t
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("repeat comparison after mutating returned records: %v", err)
|
t.Fatalf("repeat comparison after mutating returned records: %v", err)
|
||||||
}
|
}
|
||||||
if len(again) != 2 {
|
if len(again) != 1 {
|
||||||
t.Fatalf("repeat difference count = %d, want 2: %+v", len(again), again)
|
t.Fatalf("repeat difference count = %d, want 1: %+v", len(again), again)
|
||||||
}
|
}
|
||||||
againByPath := differencesByPath(again)
|
againByPath := differencesByPath(again)
|
||||||
if _, exists := againByPath["/caller/mutation"]; exists {
|
if _, exists := againByPath["/caller/mutation"]; exists {
|
||||||
t.Fatal("mutating returned path reached retained inspection")
|
t.Fatal("mutating returned path reached retained inspection")
|
||||||
}
|
}
|
||||||
if _, exists := againByPath["/backends/local/extra_params/token"]; !exists {
|
if _, exists := againByPath["/backends"]; !exists {
|
||||||
t.Fatalf("original sensitive path was lost after mutation: %+v", again)
|
t.Fatalf("original backend path was lost after mutation: %+v", again)
|
||||||
}
|
|
||||||
if _, exists := againByPath["/backends/local/extra_params/visible"]; !exists {
|
|
||||||
t.Fatalf("original public path was lost after mutation: %+v", again)
|
|
||||||
}
|
}
|
||||||
for _, difference := range again {
|
for _, difference := range again {
|
||||||
if difference.Left.YAML == "caller mutation" || difference.Right.YAML == "caller mutation" {
|
if difference.Left.YAML == "caller mutation" || difference.Right.YAML == "caller mutation" {
|
||||||
@@ -183,7 +173,7 @@ func TestConfigurationInspectionDiffMasksSensitiveShapeReplacement(t *testing.T)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("compare sensitive shape replacement: %v", err)
|
t.Fatalf("compare sensitive shape replacement: %v", err)
|
||||||
}
|
}
|
||||||
if len(differences) != 1 || differences[0].Path.String() != "/backends/local/extra_params/details" {
|
if len(differences) != 1 || differences[0].Path.String() != "/backends" {
|
||||||
t.Fatalf("shape replacement differences = %+v", differences)
|
t.Fatalf("shape replacement differences = %+v", differences)
|
||||||
}
|
}
|
||||||
difference := differences[0]
|
difference := differences[0]
|
||||||
@@ -202,16 +192,16 @@ func comparisonInspection(t *testing.T, model string) promptkit.ConfigurationIns
|
|||||||
|
|
||||||
func comparisonInspectionFrom(t *testing.T, source, model string, extraParams map[string]any) promptkit.ConfigurationInspection {
|
func comparisonInspectionFrom(t *testing.T, source, model string, extraParams map[string]any) promptkit.ConfigurationInspection {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
backend := map[string]any{"endpoint": "https://example.invalid/v1"}
|
backend := map[string]any{"id": "local", "endpoint": "https://example.invalid/v1"}
|
||||||
if extraParams != nil {
|
if extraParams != nil {
|
||||||
backend["extra_params"] = extraParams
|
backend["extra_params"] = extraParams
|
||||||
}
|
}
|
||||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||||
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping(source, map[string]any{
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping(source, map[string]any{
|
||||||
"backends": map[string]any{"local": backend},
|
"backends": []any{backend},
|
||||||
"profiles": map[string]any{"offline": map[string]any{
|
"profiles": []any{map[string]any{
|
||||||
"backend": "local",
|
"id": "offline", "backend": "local",
|
||||||
"model": model,
|
"model": model,
|
||||||
}},
|
}},
|
||||||
})}}}},
|
})}}}},
|
||||||
}})
|
}})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -19,23 +20,26 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
|||||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||||
Underlays: []configkit.Underlay{
|
Underlays: []configkit.Underlay{
|
||||||
{Inputs: []configkit.Input{{Source: configkit.Mapping("defaults", map[string]any{
|
{Inputs: []configkit.Input{{Source: configkit.Mapping("defaults", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{
|
||||||
"endpoint": "https://example.invalid/v1", "api_key_env": "OPTIONAL_KEY",
|
"id": "local", "endpoint": "https://example.invalid/v1", "api_key_env": "OPTIONAL_KEY",
|
||||||
"extra_params": map[string]any{"token": "private-sentinel", "visible": "public-value"},
|
"extra_params": map[string]any{"token": "private-sentinel", "visible": "public-value"},
|
||||||
}},
|
}},
|
||||||
"collections": map[string]any{"literal": map[string]any{
|
"collections": map[string]any{"literal": map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{
|
||||||
"endpoint": "https://example.invalid/v1",
|
map[string]any{"id": "local", "endpoint": "https://example.invalid/v1", "extra_params": map[string]any{"token": "namespaced-private-sentinel"}},
|
||||||
"extra_params": map[string]any{"token": "namespaced-private-sentinel"},
|
map[string]any{"id": "child", "extends": map[string]any{"id": "local"}},
|
||||||
}},
|
},
|
||||||
"profiles": map[string]any{
|
"profiles": []any{
|
||||||
"base.v1": map[string]any{"backend": "local", "model": "default-model"},
|
map[string]any{"id": "base.v1", "backend": "local", "model": "default-model"},
|
||||||
"id": map[string]any{"extends": "base.v1"},
|
map[string]any{"id": "id", "extends": map[string]any{"id": "base.v1"}},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
})}}},
|
})}}},
|
||||||
{Inputs: []configkit.Input{{Source: configkit.Mapping("operator", map[string]any{
|
{Inputs: []configkit.Input{{Source: configkit.Mapping("operator", map[string]any{
|
||||||
"collections": map[string]any{"literal": map[string]any{"profiles": map[string]any{"base.v1": map[string]any{"model": "operator-model"}}}},
|
"collections": map[string]any{"literal": map[string]any{
|
||||||
|
"backends": []any{map[string]any{"id": "local", "endpoint": "https://example.invalid/v1", "extra_params": map[string]any{"token": "replacement-private-sentinel"}}},
|
||||||
|
"profiles": []any{map[string]any{"id": "base.v1", "backend": "local", "model": "operator-model"}},
|
||||||
|
}},
|
||||||
})}}},
|
})}}},
|
||||||
},
|
},
|
||||||
}})
|
}})
|
||||||
@@ -46,29 +50,38 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("inspect configuration: %v", err)
|
t.Fatalf("inspect configuration: %v", err)
|
||||||
}
|
}
|
||||||
modelPath := configkit.Keys("collections", "literal", "profiles", "base.v1", "model")
|
modelPath := configkit.Keys("collections", "literal", "profiles").Index(0).Key("model")
|
||||||
model, found := view.Explain(modelPath)
|
model, found := view.Explain(modelPath)
|
||||||
if !found || !strings.Contains(model.Effective.YAML, "operator-model") || len(model.History) < 2 {
|
if !found || !strings.Contains(model.Effective.YAML, "operator-model") || len(model.History) == 0 {
|
||||||
t.Fatalf("layered model explanation = (%+v, %t)", model, found)
|
t.Fatalf("layered model explanation = (%+v, %t)", model, found)
|
||||||
}
|
}
|
||||||
inheritedPath := configkit.Keys("collections", "literal", "profiles", "id", "model")
|
inheritedPath := configkit.Keys("collections", "literal", "profiles").Index(1).Key("model")
|
||||||
inherited, found := view.Explain(inheritedPath)
|
inherited, found := view.Explain(inheritedPath)
|
||||||
if !found || !strings.Contains(inherited.Effective.YAML, "operator-model") || len(inherited.History) == 0 || !view.Metadata().IsExplicit(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)
|
t.Fatalf("literal-key inheritance explanation = (%+v, %t)", inherited, found)
|
||||||
}
|
}
|
||||||
if inheritedPath.String() != "/collections/literal/profiles/id/model" {
|
if inheritedPath.String() != "/collections/literal/profiles/1/model" {
|
||||||
t.Fatalf("literal-key path = %q", inheritedPath)
|
t.Fatalf("literal-key path = %q", inheritedPath)
|
||||||
}
|
}
|
||||||
secretPath := configkit.Keys("backends", "local", "extra_params", "token")
|
secretPath := configkit.Keys("backends").Index(0).Key("extra_params").Key("token")
|
||||||
secret, found := view.Explain(secretPath)
|
secret, found := view.Explain(secretPath)
|
||||||
if !found || !secret.Effective.Redacted || strings.Contains(secret.Effective.YAML, "private-sentinel") {
|
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)
|
t.Fatalf("credential-shaped provider value was not redacted: (%+v, %t)", secret, found)
|
||||||
}
|
}
|
||||||
namespacedSecretPath := configkit.Keys("collections", "literal", "backends", "local", "extra_params", "token")
|
namespacedSecretPath := configkit.Keys("collections", "literal", "backends").Index(0).Key("extra_params").Key("token")
|
||||||
namespacedSecret, found := view.Explain(namespacedSecretPath)
|
namespacedSecret, found := view.Explain(namespacedSecretPath)
|
||||||
if !found || !namespacedSecret.Effective.Redacted || strings.Contains(namespacedSecret.Effective.YAML, "namespaced-private-sentinel") {
|
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)
|
t.Fatalf("namespaced provider value was not redacted: (%+v, %t)", namespacedSecret, found)
|
||||||
}
|
}
|
||||||
|
inheritedSecretPath := configkit.Keys("collections", "literal", "backends").Index(1).Key("extra_params").Key("token")
|
||||||
|
inheritedSecret, found := view.Explain(inheritedSecretPath)
|
||||||
|
if !found || !inheritedSecret.Effective.Redacted || strings.Contains(inheritedSecret.Effective.YAML, "replacement-private-sentinel") {
|
||||||
|
t.Fatalf("inherited provider value was not redacted: (%+v, %t)", inheritedSecret, found)
|
||||||
|
}
|
||||||
|
metadata := fmt.Sprintf("%+v", view.Metadata().History(inheritedSecretPath))
|
||||||
|
if strings.Contains(metadata, "replacement-private-sentinel") || strings.Contains(metadata, "namespaced-private-sentinel") {
|
||||||
|
t.Fatal("metadata disclosed a sensitive provider value")
|
||||||
|
}
|
||||||
var effective bytes.Buffer
|
var effective bytes.Buffer
|
||||||
if err := view.WriteEffective(&effective); err != nil {
|
if err := view.WriteEffective(&effective); err != nil {
|
||||||
t.Fatalf("write effective configuration: %v", err)
|
t.Fatalf("write effective configuration: %v", err)
|
||||||
@@ -104,9 +117,26 @@ func TestConfigurationInspectionReportsLiteralKeysAndRedactedHistory(t *testing.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigurationInspectionFailureDoesNotDiscloseSensitiveValues(t *testing.T) {
|
||||||
|
_, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{
|
||||||
|
Source: configkit.Mapping("invalid backend", map[string]any{
|
||||||
|
"collections": map[string]any{"private": map[string]any{"backends": []any{
|
||||||
|
map[string]any{"id": "backend", "endpoint": "invalid endpoint", "extra_params": map[string]any{"token": "failure-private-sentinel"}},
|
||||||
|
}}},
|
||||||
|
}),
|
||||||
|
}}})
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if !errors.As(err, &loadErr) {
|
||||||
|
t.Fatalf("invalid backend error = %v, want load error", err)
|
||||||
|
}
|
||||||
|
if text := fmt.Sprintf("%v %+v", err, loadErr.Diagnostics()); strings.Contains(text, "failure-private-sentinel") {
|
||||||
|
t.Fatalf("failure diagnostics disclosed a sensitive value: %s", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigurationInspectionRetainsMainSpecificationDeclaration(t *testing.T) {
|
func TestConfigurationInspectionRetainsMainSpecificationDeclaration(t *testing.T) {
|
||||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{
|
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{
|
||||||
Source: configkit.Mapping("main", map[string]any{"version": "1"}),
|
Source: configkit.Mapping("main", map[string]any{"version": "2"}),
|
||||||
}}})
|
}}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("construct engine with accepted declaration: %v", err)
|
t.Fatalf("construct engine with accepted declaration: %v", err)
|
||||||
@@ -117,7 +147,7 @@ func TestConfigurationInspectionRetainsMainSpecificationDeclaration(t *testing.T
|
|||||||
}
|
}
|
||||||
path := configkit.Keys("version")
|
path := configkit.Keys("version")
|
||||||
field, found := inspection.Explain(path)
|
field, found := inspection.Explain(path)
|
||||||
if !found || !inspection.Metadata().IsExplicit(path) || !strings.Contains(field.Effective.YAML, "1") {
|
if !found || !inspection.Metadata().IsExplicit(path) || !strings.Contains(field.Effective.YAML, "2") {
|
||||||
t.Fatalf("main specification declaration was not retained: (%+v, %t)", field, found)
|
t.Fatalf("main specification declaration was not retained: (%+v, %t)", field, found)
|
||||||
}
|
}
|
||||||
var effective bytes.Buffer
|
var effective bytes.Buffer
|
||||||
@@ -150,9 +180,8 @@ func TestConfigurationInspectionZeroAndWriterErrors(t *testing.T) {
|
|||||||
t.Fatalf("zero explanation writer = %v", err)
|
t.Fatalf("zero explanation writer = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint, model := "https://configured.example/v1", "configured-model"
|
profile := configkit.Input{Source: configkit.Mapping("inspection test profile", map[string]any{
|
||||||
profile := configkit.Input{Source: promptkit.ProfileSource("inspection test profile", map[string]promptkit.ProfileConfig{
|
"profiles": []any{map[string]any{"id": "inspection-profile", "endpoint": "https://configured.example/v1", "model": "configured-model"}},
|
||||||
"inspection-profile": {Endpoint: &endpoint, Model: &model},
|
|
||||||
})}
|
})}
|
||||||
valid, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
valid, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||||
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{profile}}},
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{profile}}},
|
||||||
@@ -169,14 +198,15 @@ func TestConfigurationInspectionZeroAndWriterErrors(t *testing.T) {
|
|||||||
if err := view.WriteEffective(writer); !errors.Is(err, writeErr) {
|
if err := view.WriteEffective(writer); !errors.Is(err, writeErr) {
|
||||||
t.Fatalf("effective writer error = %v", err)
|
t.Fatalf("effective writer error = %v", err)
|
||||||
}
|
}
|
||||||
if err := view.WriteExplanation(writer, configkit.Keys("profiles", "inspection-profile", "model")); !errors.Is(err, writeErr) {
|
if err := view.WriteExplanation(writer, configkit.Keys("profiles").Index(0).Key("model")); !errors.Is(err, writeErr) {
|
||||||
t.Fatalf("explanation writer error = %v", err)
|
t.Fatalf("explanation writer error = %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigurationInspectionUsesCapturedSourcesWithoutProviderCalls(t *testing.T) {
|
func TestConfigurationInspectionUsesCapturedSourcesWithoutProviderCalls(t *testing.T) {
|
||||||
|
const promptDocument = "prompts:\n - id: captured\n default_profile: offline\n messages:\n - role: user\n content_file: body.tmpl\n output:\n format: text\n validation_mode: none\n"
|
||||||
source := &unreadableAfterConstructionFS{FS: fstest.MapFS{
|
source := &unreadableAfterConstructionFS{FS: fstest.MapFS{
|
||||||
"config/prompt.yaml": &fstest.MapFile{Data: []byte(capturedPromptDocument)},
|
"config/prompt.yaml": &fstest.MapFile{Data: []byte(promptDocument)},
|
||||||
"config/body.tmpl": &fstest.MapFile{Data: []byte("hello")},
|
"config/body.tmpl": &fstest.MapFile{Data: []byte("hello")},
|
||||||
}}
|
}}
|
||||||
inputs, err := promptkit.FilesystemInputs("inspection source", source, "config")
|
inputs, err := promptkit.FilesystemInputs("inspection source", source, "config")
|
||||||
@@ -184,7 +214,7 @@ func TestConfigurationInspectionUsesCapturedSourcesWithoutProviderCalls(t *testi
|
|||||||
t.Fatalf("select inputs: %v", err)
|
t.Fatalf("select inputs: %v", err)
|
||||||
}
|
}
|
||||||
otherSource := &unreadableAfterConstructionFS{FS: fstest.MapFS{
|
otherSource := &unreadableAfterConstructionFS{FS: fstest.MapFS{
|
||||||
"config/prompt.yaml": &fstest.MapFile{Data: []byte(capturedPromptDocument)},
|
"config/prompt.yaml": &fstest.MapFile{Data: []byte(promptDocument)},
|
||||||
"config/body.tmpl": &fstest.MapFile{Data: []byte("hello")},
|
"config/body.tmpl": &fstest.MapFile{Data: []byte("hello")},
|
||||||
}}
|
}}
|
||||||
otherInputs, err := promptkit.FilesystemInputs("comparison source", otherSource, "config")
|
otherInputs, err := promptkit.FilesystemInputs("comparison source", otherSource, "config")
|
||||||
@@ -195,16 +225,16 @@ func TestConfigurationInspectionUsesCapturedSourcesWithoutProviderCalls(t *testi
|
|||||||
client := countingLLMClient{calls: &modelCalls}
|
client := countingLLMClient{calls: &modelCalls}
|
||||||
endpoint, model, otherModel := "https://example.invalid/v1", "offline-model", "comparison-model"
|
endpoint, model, otherModel := "https://example.invalid/v1", "offline-model", "comparison-model"
|
||||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||||
Underlays: []configkit.Underlay{{Inputs: inputs}, {Inputs: []configkit.Input{{Source: promptkit.ProfileSource("offline profile", map[string]promptkit.ProfileConfig{
|
Underlays: []configkit.Underlay{{Inputs: inputs}, {Inputs: []configkit.Input{{Source: configkit.Mapping("offline profile", map[string]any{
|
||||||
"offline": {Endpoint: &endpoint, Model: &model},
|
"profiles": []any{map[string]any{"id": "offline", "endpoint": endpoint, "model": model}},
|
||||||
})}}}},
|
})}}}},
|
||||||
}}, promptkit.WithLLMClient(client))
|
}}, promptkit.WithLLMClient(client))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("construct engine: %v", err)
|
t.Fatalf("construct engine: %v", err)
|
||||||
}
|
}
|
||||||
otherEngine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
otherEngine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||||
Underlays: []configkit.Underlay{{Inputs: otherInputs}, {Inputs: []configkit.Input{{Source: promptkit.ProfileSource("comparison profile", map[string]promptkit.ProfileConfig{
|
Underlays: []configkit.Underlay{{Inputs: otherInputs}, {Inputs: []configkit.Input{{Source: configkit.Mapping("comparison profile", map[string]any{
|
||||||
"offline": {Endpoint: &endpoint, Model: &otherModel},
|
"profiles": []any{map[string]any{"id": "offline", "endpoint": endpoint, "model": otherModel}},
|
||||||
})}}}},
|
})}}}},
|
||||||
}}, promptkit.WithLLMClient(client))
|
}}, promptkit.WithLLMClient(client))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -226,7 +256,7 @@ func TestConfigurationInspectionUsesCapturedSourcesWithoutProviderCalls(t *testi
|
|||||||
if err != nil || len(differences) != 1 {
|
if err != nil || len(differences) != 1 {
|
||||||
t.Fatalf("compare captured configurations = (%+v, %v), want one difference", differences, err)
|
t.Fatalf("compare captured configurations = (%+v, %v), want one difference", differences, err)
|
||||||
}
|
}
|
||||||
if differences[0].Path.String() != "/profiles/offline/model" ||
|
if differences[0].Path.String() != "/profiles" ||
|
||||||
!strings.Contains(differences[0].Left.YAML, model) ||
|
!strings.Contains(differences[0].Left.YAML, model) ||
|
||||||
!strings.Contains(differences[0].Right.YAML, otherModel) {
|
!strings.Contains(differences[0].Right.YAML, otherModel) {
|
||||||
t.Fatalf("captured difference = %+v", differences[0])
|
t.Fatalf("captured difference = %+v", differences[0])
|
||||||
@@ -238,7 +268,7 @@ func TestConfigurationInspectionUsesCapturedSourcesWithoutProviderCalls(t *testi
|
|||||||
t.Fatalf("captured difference changed between reads: first=%+v next=%+v", expected, differences[0])
|
t.Fatalf("captured difference changed between reads: first=%+v next=%+v", expected, differences[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, found := view.Explain(configkit.Keys("prompts", "captured", "messages").Index(0).Key("content_file")); !found {
|
if _, found := view.Explain(configkit.Keys("prompts").Index(0).Key("messages").Index(0).Key("content_file")); !found {
|
||||||
t.Fatal("captured prompt content path was not inspectable")
|
t.Fatal("captured prompt content path was not inspectable")
|
||||||
}
|
}
|
||||||
var report bytes.Buffer
|
var report bytes.Buffer
|
||||||
|
|||||||
@@ -359,6 +359,14 @@ Validation: converted inspection/diff tests find the expected values/origins and
|
|||||||
do not disclose synthetic credentials. Relax only assertions tied to deliberate
|
do not disclose synthetic credentials. Relax only assertions tied to deliberate
|
||||||
upstream history/diagnostic changes, not meaningful redaction expectations.
|
upstream history/diagnostic changes, not meaningful redaction expectations.
|
||||||
|
|
||||||
|
Completion: sensitivity matching and inspection paths now recognize resource
|
||||||
|
list indexes. Public inspection and diff fixtures use identified lists and
|
||||||
|
ConfigKit's atomic sequence comparison, including replaced and inherited
|
||||||
|
sensitive values, metadata, failure diagnostics, and detached reports. Validation
|
||||||
|
passed: `go test . -run '^TestConfigurationInspection' -count=1` with Go 1.27.0,
|
||||||
|
formatting, and `git diff --check`. Internal package tests remain deferred until
|
||||||
|
the remaining map-indexed loader fixtures are converted.
|
||||||
|
|
||||||
## Stage 8: Convert Public Source Builders To Explicit-ID Slices
|
## Stage 8: Convert Public Source Builders To Explicit-ID Slices
|
||||||
|
|
||||||
Scope: `profiles.go`, `backends.go`, source-builder GoDoc, and owning construction
|
Scope: `profiles.go`, `backends.go`, source-builder GoDoc, and owning construction
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package configuration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.maximumdirect.net/configkit"
|
"go.maximumdirect.net/configkit"
|
||||||
@@ -66,11 +67,16 @@ func secretProviderPath(path configkit.Path) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func providerExtraParamsPath(parts []string) bool {
|
func providerExtraParamsPath(parts []string) bool {
|
||||||
if len(parts) >= 4 && parts[1] != "" &&
|
if len(parts) >= 4 && sequenceIndex(parts[1]) &&
|
||||||
(parts[0] == "backends" || parts[0] == "profiles") && parts[2] == "extra_params" {
|
(parts[0] == "backends" || parts[0] == "profiles") && parts[2] == "extra_params" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return len(parts) >= 6 && parts[0] == "collections" && parts[1] != "" &&
|
return len(parts) >= 6 && parts[0] == "collections" && parts[1] != "" &&
|
||||||
(parts[2] == "backends" || parts[2] == "profiles") &&
|
(parts[2] == "backends" || parts[2] == "profiles") &&
|
||||||
parts[3] != "" && parts[4] == "extra_params"
|
sequenceIndex(parts[3]) && parts[4] == "extra_params"
|
||||||
|
}
|
||||||
|
|
||||||
|
func sequenceIndex(segment string) bool {
|
||||||
|
index, err := strconv.Atoi(segment)
|
||||||
|
return err == nil && index >= 0 && strconv.Itoa(index) == segment
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ func TestSecretProviderPathRecognizesResourceExtraParams(t *testing.T) {
|
|||||||
path configkit.Path
|
path configkit.Path
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{name: "backend nested secret", path: configkit.Keys("backends", "provider", "extra_params", "nested", "Token"), want: true},
|
{name: "backend nested secret", path: configkit.Keys("backends").Index(0).Key("extra_params").Key("nested").Key("Token"), want: true},
|
||||||
{name: "profile nested secret", path: configkit.Keys("profiles", "assistant", "extra_params", "authorization"), want: true},
|
{name: "profile nested secret", path: configkit.Keys("profiles").Index(1).Key("extra_params").Key("authorization"), want: true},
|
||||||
{name: "collection backend nested secret", path: configkit.Keys("collections", "provider", "backends", "default", "extra_params", "nested", "Token"), want: true},
|
{name: "collection backend nested secret", path: configkit.Keys("collections", "provider", "backends").Index(2).Key("extra_params").Key("nested").Key("Token"), want: true},
|
||||||
{name: "collection profile secret", path: configkit.Keys("collections", "assistant", "profiles", "model", "extra_params", "authorization"), want: true},
|
{name: "collection profile secret", path: configkit.Keys("collections", "assistant", "profiles").Index(0).Key("extra_params").Key("authorization"), want: true},
|
||||||
{name: "unrelated nested mapping", path: configkit.Keys("application", "backends", "provider", "extra_params", "token"), want: false},
|
{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: "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: "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: "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},
|
{name: "ordinary provider value", path: configkit.Keys("profiles").Index(0).Key("extra_params").Key("provider").Key("mode"), want: false},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user