Verify native profile runtime behavior
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# Implementation Plan: PromptKit v0.12.0 Adoption
|
# Implementation Plan: PromptKit v0.12.0 Adoption
|
||||||
|
|
||||||
**Last updated:** 2026-10-04
|
**Last updated:** 2026-10-04
|
||||||
**Status:** In progress; Stages 1–6 completed.
|
**Status:** In progress; Stages 1–7 completed.
|
||||||
**Audience:** Luna coding agent.
|
**Audience:** Luna coding agent.
|
||||||
|
|
||||||
## Scope and Execution
|
## Scope and Execution
|
||||||
@@ -407,6 +407,13 @@ integration tests where their established behavior is affected.
|
|||||||
and `git diff --check`. Review whether each new test addresses a distinct changed
|
and `git diff --check`. Review whether each new test addresses a distinct changed
|
||||||
boundary. Commit the native-profile and runtime verification unit.
|
boundary. Commit the native-profile and runtime verification unit.
|
||||||
|
|
||||||
|
**Completion marker:** Stage 7 complete. Native profile inheritance, control
|
||||||
|
presence, provider-map overlays, application backend authority, selected
|
||||||
|
credential rejection, catalog-ID handling, shared backend capacity, and safe
|
||||||
|
inspection behavior are covered. The six targeted packages passed, along with
|
||||||
|
focused wire, timeout, capacity, credential, and safe-view tests; `git diff
|
||||||
|
--check` passed.
|
||||||
|
|
||||||
## Stage 8 — Document the Migration and Update Attribution
|
## Stage 8 — Document the Migration and Update Attribution
|
||||||
|
|
||||||
**Primary files:** `docs/config.md`, `docs/internal/llm-runtime.md`,
|
**Primary files:** `docs/config.md`, `docs/internal/llm-runtime.md`,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
func TestRolesShareBackendCapacityAndReleaseAfterCancellation(t *testing.T) {
|
func TestRolesShareBackendCapacityAndReleaseAfterCancellation(t *testing.T) {
|
||||||
t.Setenv("INHERITED_TEST_KEY", "synthetic-inherited-runtime-secret")
|
t.Setenv("INHERITED_TEST_KEY", "synthetic-inherited-runtime-secret")
|
||||||
|
t.Setenv("OPERATOR_BACKEND_KEY", "synthetic-operator-backend-secret")
|
||||||
entered := make(chan struct{}, 3)
|
entered := make(chan struct{}, 3)
|
||||||
release := make(chan struct{})
|
release := make(chan struct{})
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -51,6 +52,16 @@ func TestRolesShareBackendCapacityAndReleaseAfterCancellation(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
operatorBackend := `backends:
|
||||||
|
shared:
|
||||||
|
endpoint: https://operator.invalid/v1
|
||||||
|
concurrency_limit: 77
|
||||||
|
queue_capacity: 88
|
||||||
|
api_key_env: OPERATOR_BACKEND_KEY
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "backend-tuning.yaml"), []byte(operatorBackend), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
loaded, err := config.Load(ck.File(main), "")
|
loaded, err := config.Load(ck.File(main), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -30,10 +30,22 @@ type reply struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func adapterFixture(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request)) (*Runtime, *atomic.Int32, string) {
|
func adapterFixture(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request)) (*Runtime, *atomic.Int32, string) {
|
||||||
return adapterFixtureWithPrompt(t, replies, retries, repairs, hook, "")
|
return adapterFixtureWithDocuments(t, replies, retries, repairs, hook, "offline", map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func adapterFixtureWithPrompt(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request), operatorPrompt string) (*Runtime, *atomic.Int32, string) {
|
func adapterFixtureWithPrompt(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request), operatorPrompt string) (*Runtime, *atomic.Int32, string) {
|
||||||
|
documents := map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n",
|
||||||
|
}
|
||||||
|
if operatorPrompt != "" {
|
||||||
|
documents["operator-prompt.yaml"] = operatorPrompt
|
||||||
|
}
|
||||||
|
return adapterFixtureWithDocuments(t, replies, retries, repairs, hook, "offline", documents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adapterFixtureWithDocuments(t *testing.T, replies []reply, retries, repairs int, hook func(*http.Request), backendID string, documents map[string]string) (*Runtime, *atomic.Int32, string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var count atomic.Int32
|
var count atomic.Int32
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -69,15 +81,18 @@ func adapterFixtureWithPrompt(t *testing.T, replies []reply, retries, repairs in
|
|||||||
}))
|
}))
|
||||||
t.Cleanup(server.Close)
|
t.Cleanup(server.Close)
|
||||||
t.Setenv("ADAPTER_KEY", "adapter-secret")
|
t.Setenv("ADAPTER_KEY", "adapter-secret")
|
||||||
base := fmt.Sprintf("version: 2\nbackends:\n offline:\n endpoint: %s/v1\n credential: ADAPTER_KEY\nllm:\n proposal_profile: base\n transient_retries: %d\n repair_attempts: %d\n", server.URL, retries, repairs)
|
dir := t.TempDir()
|
||||||
dir := profileLayout(t, base, "")
|
profileDirectory := filepath.Join(dir, "profiles")
|
||||||
main := filepath.Join(dir, "config.yml")
|
if err := os.Mkdir(profileDirectory, 0700); err != nil {
|
||||||
profile := "profiles:\n base:\n backend: offline\n model: full-model\n"
|
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "base.yaml"), []byte(profile), 0600); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if operatorPrompt != "" {
|
base := fmt.Sprintf("version: 2\nprofiles_dir: profiles\nbackends:\n %s:\n endpoint: %s/v1\n credential: ADAPTER_KEY\n concurrency: 1\n queue_capacity: 0\nllm:\n proposal_profile: base\n transient_retries: %d\n repair_attempts: %d\n", backendID, server.URL, retries, repairs)
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "operator-prompt.yaml"), []byte(operatorPrompt), 0600); err != nil {
|
main := filepath.Join(dir, "config.yml")
|
||||||
|
if err := os.WriteFile(main, []byte(base), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, content := range documents {
|
||||||
|
if err := os.WriteFile(filepath.Join(profileDirectory, name), []byte(content), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +403,91 @@ func TestOperatorPromptOverridesUsePerCallSchemaAndOptionalSharedText(t *testing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProfilePresenceBackendAuthorityAndProviderParametersReachWire(t *testing.T) {
|
||||||
|
t.Setenv("CATALOG_TRAP_KEY", "catalog-secret-must-not-be-used")
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
parentTemperature string
|
||||||
|
childTemperature string
|
||||||
|
wantTemperature bool
|
||||||
|
}{
|
||||||
|
{name: "explicit zero", parentTemperature: " temperature: 0.8\n", childTemperature: " temperature: 0\n", wantTemperature: true},
|
||||||
|
{name: "omitted", wantTemperature: false},
|
||||||
|
{name: "null clears inherited control", parentTemperature: " temperature: 0.8\n", childTemperature: " temperature: null\n", wantTemperature: false},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
profileDocument := "profiles:\n \"parent~/partial\":\n backend: openrouter\n model: parent-model\n" + tc.parentTemperature +
|
||||||
|
" base:\n extends: \"parent~/partial\"\n model: full-model\n timeout_seconds: 0\n" + tc.childTemperature +
|
||||||
|
" extra_params:\n routing:\n overridden: profile\n profile_only: retained\n cleared: null\n"
|
||||||
|
operatorBackend := `backends:
|
||||||
|
openrouter:
|
||||||
|
endpoint: https://operator.invalid/v1
|
||||||
|
concurrency_limit: 91
|
||||||
|
queue_capacity: 92
|
||||||
|
api_key_env: CATALOG_TRAP_KEY
|
||||||
|
extra_params:
|
||||||
|
routing:
|
||||||
|
inherited: backend
|
||||||
|
overridden: backend
|
||||||
|
cleared: remove-me
|
||||||
|
`
|
||||||
|
documents := map[string]string{
|
||||||
|
"unrelated-profile-file.yml": profileDocument,
|
||||||
|
"provider-tuning.yaml": operatorBackend,
|
||||||
|
}
|
||||||
|
var wire map[string]json.RawMessage
|
||||||
|
runtime, count, _ := adapterFixtureWithDocuments(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, func(r *http.Request) {
|
||||||
|
if got := r.Header.Get("Authorization"); got != "Bearer adapter-secret" {
|
||||||
|
t.Errorf("application credential was not authoritative: %q", got)
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&wire); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}, "openrouter", documents)
|
||||||
|
profile := runtime.Profiles["proposal"]
|
||||||
|
if profile.EffectiveModelParams.APIKeyEnv != "" || profile.EffectiveModelParams.Endpoint == "https://operator.invalid/v1" || profile.EffectiveModelParams.TimeoutSeconds != 0 {
|
||||||
|
t.Fatalf("application backend mapping or zero timeout lost: %+v", profile.EffectiveModelParams)
|
||||||
|
}
|
||||||
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var out map[string]any
|
||||||
|
response, err := client.CompleteStructured(context.Background(), adapterRequest(responseschema.CorrectionSetKey), &out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if count.Load() != 1 {
|
||||||
|
t.Fatalf("provider calls = %d, want 1", count.Load())
|
||||||
|
}
|
||||||
|
temperature, hasTemperature := wire["temperature"]
|
||||||
|
if hasTemperature != tc.wantTemperature {
|
||||||
|
t.Fatalf("temperature presence = %t, want %t: %s", hasTemperature, tc.wantTemperature, wire)
|
||||||
|
}
|
||||||
|
if tc.wantTemperature {
|
||||||
|
var value float64
|
||||||
|
if err := json.Unmarshal(temperature, &value); err != nil || value != 0 {
|
||||||
|
t.Fatalf("explicit zero temperature = %s (%v)", temperature, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var routing map[string]any
|
||||||
|
if err := json.Unmarshal(wire["routing"], &routing); err != nil {
|
||||||
|
t.Fatalf("provider routing fields missing: %s (%v)", wire, err)
|
||||||
|
}
|
||||||
|
if routing["inherited"] != "backend" || routing["overridden"] != "profile" || routing["profile_only"] != "retained" {
|
||||||
|
t.Fatalf("recursively overlaid provider fields = %#v", routing)
|
||||||
|
}
|
||||||
|
if value, exists := routing["cleared"]; !exists || value != nil {
|
||||||
|
t.Fatalf("null did not replace inherited provider field: %#v", routing)
|
||||||
|
}
|
||||||
|
if response.Model != "full-model" || response.Prepared == nil {
|
||||||
|
t.Fatalf("execution did not use selected profile: %+v", response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPreparedAdapterUsesCapturedProfileAndPromptUntilReconstructed(t *testing.T) {
|
func TestPreparedAdapterUsesCapturedProfileAndPromptUntilReconstructed(t *testing.T) {
|
||||||
var posted []struct {
|
var posted []struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
@@ -409,7 +509,7 @@ func TestPreparedAdapterUsesCapturedProfileAndPromptUntilReconstructed(t *testin
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
profile := "profiles:\n base:\n backend: offline\n model: edited-model\n"
|
profile := "profiles:\n base:\n backend: offline\n model: edited-model\n"
|
||||||
if err := os.WriteFile(filepath.Join(dir, "profiles", "base.yaml"), []byte(profile), 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "profiles", "definitions.yaml"), []byte(profile), 0600); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
updatedPrompt := operatorPromptDocument("Edited operator instructions.", "Edited payload.")
|
updatedPrompt := operatorPromptDocument("Edited operator instructions.", "Edited payload.")
|
||||||
@@ -480,7 +580,9 @@ func TestPreparedAdapterCancellationStopsRetryWait(t *testing.T) {
|
|||||||
|
|
||||||
func TestPreparedAdapterDistinguishesOperationTimeoutFromParentCancellation(t *testing.T) {
|
func TestPreparedAdapterDistinguishesOperationTimeoutFromParentCancellation(t *testing.T) {
|
||||||
blocking := func(*http.Request) { time.Sleep(500 * time.Millisecond) }
|
blocking := func(*http.Request) { time.Sleep(500 * time.Millisecond) }
|
||||||
runtime, _, _ := adapterFixture(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, blocking)
|
runtime, _, _ := adapterFixtureWithDocuments(t, []reply{{200, `{"corrections":[]}`}}, 0, 0, blocking, "offline", map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n timeout_seconds: 0\n",
|
||||||
|
})
|
||||||
runtime.Config.LLM.OperationTimeout = 200 * time.Millisecond
|
runtime.Config.LLM.OperationTimeout = 200 * time.Millisecond
|
||||||
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
client, err := runtime.NewRoleClient("proposal", CallHooks{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -124,6 +124,46 @@ func TestNewRuntimeAllowsUnselectedPartialProfiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNativeProfileInheritancePreservesControlsAndMergesProviderValues(t *testing.T) {
|
||||||
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: \"child profile\"\n", map[string]string{
|
||||||
|
"unrelated-name.yaml": `profiles:
|
||||||
|
"parent~/shared":
|
||||||
|
backend: offline
|
||||||
|
temperature: 0.4
|
||||||
|
top_p: 0.7
|
||||||
|
extra_params:
|
||||||
|
routing:
|
||||||
|
retained: from-parent
|
||||||
|
overridden: parent
|
||||||
|
cleared: remove-me
|
||||||
|
"child profile":
|
||||||
|
extends: "parent~/shared"
|
||||||
|
model: child-model
|
||||||
|
extra_params:
|
||||||
|
routing:
|
||||||
|
overridden: child
|
||||||
|
child_only: present
|
||||||
|
cleared: null
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
runtime, err := NewRuntime(context.Background(), cfg, main, &http.Client{Transport: rejectingTransport{}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
profile := runtime.Profiles["proposal"]
|
||||||
|
target := profile.EffectiveModelParams
|
||||||
|
if profile.ProfileID != "child profile" || target.Model != "child-model" || target.Temperature != 0.4 || target.TopP != 0.7 {
|
||||||
|
t.Fatalf("inherited profile target = %+v", target)
|
||||||
|
}
|
||||||
|
routing, ok := target.ExtraParams["routing"].(map[string]any)
|
||||||
|
if !ok || routing["retained"] != "from-parent" || routing["overridden"] != "child" || routing["child_only"] != "present" {
|
||||||
|
t.Fatalf("recursively inherited provider values = %#v", target.ExtraParams)
|
||||||
|
}
|
||||||
|
if value, exists := routing["cleared"]; !exists || value != nil {
|
||||||
|
t.Fatalf("null did not replace the inherited provider value: %#v", routing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMissingSelectedProfileDirectoryIsOperatorInputError(t *testing.T) {
|
func TestMissingSelectedProfileDirectoryIsOperatorInputError(t *testing.T) {
|
||||||
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n", map[string]string{
|
cfg, main := runtimeAssemblyFixture(t, " proposal_profile: proposal\n", map[string]string{
|
||||||
"definitions.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\n",
|
"definitions.yml": "profiles:\n proposal:\n backend: offline\n model: proposal-model\n",
|
||||||
|
|||||||
@@ -113,6 +113,33 @@ func TestOfflineProfileAssemblyAndSafeViews(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSafeViewsOmitProviderAndPromptValues(t *testing.T) {
|
||||||
|
const providerValue = "private-provider-value"
|
||||||
|
const promptValue = "private-operator-prompt-text"
|
||||||
|
runtime, _, dir := adapterFixtureWithDocuments(t, nil, 0, 0, nil, "offline", map[string]string{
|
||||||
|
"definitions.yaml": "profiles:\n base:\n backend: offline\n model: full-model\n extra_params:\n vendor_extension: " + providerValue + "\n",
|
||||||
|
"operator-prompt.yaml": operatorPromptDocument(promptValue, promptValue),
|
||||||
|
})
|
||||||
|
profiles, err := runtime.SelectedProfilesYAML()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loaded, err := config.Load(ck.File(filepath.Join(dir, "config.yml")), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
explain, err := ExplanationYAML(loaded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
combined := string(profiles) + string(explain)
|
||||||
|
for _, value := range []string{providerValue, promptValue, "adapter-secret", "127.0.0.1"} {
|
||||||
|
if strings.Contains(combined, value) {
|
||||||
|
t.Fatalf("safe views exposed %q: %s", value, combined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExplanationYAMLIncludesSafeInheritanceEvidence(t *testing.T) {
|
func TestExplanationYAMLIncludesSafeInheritanceEvidence(t *testing.T) {
|
||||||
t.Setenv("SYNTHETIC_KEY", "synthetic-inherited-secret-never-print")
|
t.Setenv("SYNTHETIC_KEY", "synthetic-inherited-secret-never-print")
|
||||||
text := `version: 2
|
text := `version: 2
|
||||||
|
|||||||
Reference in New Issue
Block a user