Clarify credential inspection and protect request privacy

This commit is contained in:
2026-11-05 14:28:45 +00:00
parent ada5377f32
commit 59e8010623
7 changed files with 69 additions and 16 deletions

View File

@@ -719,18 +719,19 @@ if err != nil {
}
target := inspection.EffectiveModelParams
if inspection.APIKeyRequired {
// Arrange a usable direct or configured environment credential.
}
if target.APIKeyEnv != "" {
// This is a configured optional environment lookup source.
} else if inspection.APIKeyRequired {
// Arrange a direct credential before later execution.
// This is the configured environment lookup source, not its value.
}
```
Use this configuration-time boundary when only the profile and its target need
checking. Use `Prepare` when the application also needs prompt, input, schema,
or rendering work; use prepared execution when that work must remain tied to a
later execution. A reported `APIKeyEnv` is a configured optional source, while
`APIKeyRequired` is the explicit local requirement. The
later execution. A reported `APIKeyEnv` identifies a configured source, while
`APIKeyRequired` is the explicit requirement; both may be present. The
[credential format reference](../formats.md#credentials) and the method's
[GoDoc](../../engine.go) own the exact precedence, timing, result, and error
contracts.

View File

@@ -115,9 +115,10 @@ The client conditionally includes:
strict flag, and schema document. Plain JSON validation does not add an
object-only response constraint.
The engine resolves backend, profile, and request extra-parameter maps by
whole-map replacement rather than key merging. The resulting effective map is
merged into the top-level body. Empty keys, values that cannot be encoded, and
Configured profile maps recursively overlay backend defaults, while a nonempty
runtime override replaces the complete configured map. The [format reference](../formats.md#defaults-and-overrides)
owns these resolution rules. The resulting effective map is merged into the
top-level body. Empty keys, values that cannot be encoded, and
collisions with these reserved fields are rejected before any provider call:
- `model`

View File

@@ -715,6 +715,11 @@ Go 1.27.3; an injected-observer test verifies permit release before callbacks.
boundary, and changed documentation links and semantics agree with canonical
GoDoc/formats. Do not snapshot whole diagnostic strings or unrelated JSON fields.
Completion: marker-based request formatting and JSON checks cover value and
pointer forms. Profile credential inspection, exact IDs, prompt listing, and
provider-map precedence prose now match the implemented contracts. Focused
privacy, profile, inheritance, and provider tests pass on Go 1.27.3.
## Stage 28: Give Bundled Catalog Expectations One Test Owner
**Catalog:** 28. **Scope:** catalog tests, not bundled asset changes.

View File

@@ -312,8 +312,8 @@ func (e *Engine) InspectPrompt(
// override. BackendID is empty for an endpoint-only profile.
//
// APIKeyEnv in the returned target is an environment-variable name, never its
// value. APIKeyRequired instead reports a direct credential requirement and is
// mutually exclusive with a nonblank APIKeyEnv. InspectProfile neither derives
// value. APIKeyRequired reports that a credential is required; an explicit
// profile environment source can also be present. InspectProfile neither derives
// an ID from a prompt default_profile nor checks credential availability, so an
// absent or blank named environment variable is not an error.
//

View File

@@ -93,8 +93,8 @@ func (e *Engine) ListProfiles(ctx context.Context, filters ...NamespaceFilter) (
// behavior as [Engine.ListProfiles], using the prompt namespace by default.
// At most one filter is accepted.
//
// The returned slice and each Version pointer are caller-owned and safe to
// mutate while the engine is used concurrently. Empty inventories succeed.
// The returned slice is caller-owned and safe to mutate while the engine is
// used concurrently. Empty inventories succeed.
//
// Listing reports membership, not execution readiness. It does not reload
// configuration or perform resource, credential, provider, or execution work.

View File

@@ -0,0 +1,46 @@
package promptkit_test
import (
"encoding/json"
"fmt"
"strings"
"testing"
"go.maximumdirect.net/promptkit"
)
func TestRunRequestFormattingAndJSONProtectDirectCredential(t *testing.T) {
markers := []string{"secret-key-marker", "variable-marker", "artifact-body-marker", "message-body-marker", "setting-marker", "schema-path-marker"}
request := promptkit.RunRequest{
PromptID: "prompt", ProfileID: "profile", APIKey: markers[0],
Vars: map[string]string{"name": markers[1]},
Inputs: map[string]promptkit.ArtifactRef{"input": {Type: promptkit.ArtifactRefInline, Body: markers[2]}},
AppendedMessages: []promptkit.RenderedMessage{{Role: promptkit.RoleUser, Content: markers[3]}},
Execution: &promptkit.ExecutionTargetOverride{Model: markers[4]},
Validation: &promptkit.OutputContract{SchemaPath: markers[5]},
}
for _, rendered := range []string{
fmt.Sprintf("%v", request), fmt.Sprintf("%+v", request), fmt.Sprintf("%#v", request),
fmt.Sprintf("%v", &request), fmt.Sprintf("%+v", &request), fmt.Sprintf("%#v", &request),
request.String(), request.GoString(), (&request).String(), (&request).GoString(),
} {
for _, marker := range markers {
if strings.Contains(rendered, marker) {
t.Fatalf("request formatting exposes %q: %s", marker, rendered)
}
}
}
for _, value := range []any{request, &request} {
encoded, err := json.Marshal(value)
if err != nil {
t.Fatal(err)
}
var object map[string]any
if err := json.Unmarshal(encoded, &object); err != nil {
t.Fatal(err)
}
if _, found := object["APIKey"]; found || strings.Contains(string(encoded), markers[0]) {
t.Fatal("request JSON exposed the direct credential")
}
}
}

View File

@@ -363,7 +363,7 @@ type ExecutionTarget struct {
// separate from that target to preserve ExecutionTarget's general execution
// and stable JSON contracts.
type ProfileInspection struct {
// ProfileID is the trimmed, exact profile ID inspected by the engine.
// ProfileID is the exact profile ID inspected by the engine.
ProfileID string
// EffectiveModelParams contains settings resolved from the framework timeout
// baseline, selected backend, and then profile, without a request override.
@@ -371,9 +371,9 @@ type ProfileInspection struct {
// provider default. APIKeyEnv is an environment-variable name, never its
// credential value.
EffectiveModelParams ExecutionTarget
// APIKeyRequired reports that a later execution must supply a direct API
// key or an explicit request environment override. It is mutually exclusive
// with a nonblank EffectiveModelParams.APIKeyEnv.
// APIKeyRequired reports that a later execution must supply a usable direct
// key or environment credential. An explicit profile environment source can
// coexist with this requirement.
APIKeyRequired bool
}