Expose safe inheritance origins in explanations
This commit is contained in:
11
docs/cli.md
11
docs/cli.md
@@ -74,9 +74,14 @@ YAML summary of selected ID, backend, model, sampling controls, per-attempt
|
|||||||
timeout, service tier, and reasoning effort for each role. It omits credentials,
|
timeout, service tier, and reasoning effort for each role. It omits credentials,
|
||||||
endpoints, and arbitrary extra provider parameters. Explain writes a YAML
|
endpoints, and arbitrary extra provider parameters. Explain writes a YAML
|
||||||
sequence of field paths, source origins, and recorded derivations without
|
sequence of field paths, source origins, and recorded derivations without
|
||||||
resolved setting values. These views share the same settings and profile
|
resolved setting values. An inherited field may also include an `inheritance`
|
||||||
validation path as `process`. Unknown views are usage errors. The effective
|
sequence; each step identifies `from`, `to`, and `reference` paths as JSON
|
||||||
settings YAML is for inspection and is not guaranteed reloadable.
|
pointers. These fields describe the effective inheritance chain without
|
||||||
|
including configuration values. A direct child override reports its own source
|
||||||
|
instead. Debug configuration origins use the same explanation shape. These views
|
||||||
|
share the same settings and profile validation path as `process`. Unknown views
|
||||||
|
are usage errors. The effective settings YAML is for inspection and is not
|
||||||
|
guaranteed reloadable.
|
||||||
|
|
||||||
For `process`, `--output` writes the corrected transcript to a file and leaves
|
For `process`, `--output` writes the corrected transcript to a file and leaves
|
||||||
stdout empty on success. Without it, stdout carries transcript JSON only on
|
stdout empty on success. Without it, stdout carries transcript JSON only on
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ Counts distinguish provider attempts, transient retries, and structural
|
|||||||
repairs, including on errors. Secrets from all configured backends, including unselected
|
repairs, including on errors. Secrets from all configured backends, including unselected
|
||||||
ones, are available to the diagnostics redaction boundary.
|
ones, are available to the diagnostics redaction boundary.
|
||||||
|
|
||||||
|
CLI explain output and debug configuration origins share a value-free projection
|
||||||
|
of effective field origins. The [CLI contract](../cli.md) defines its optional
|
||||||
|
inheritance evidence fields.
|
||||||
|
|
||||||
## Orchestration
|
## Orchestration
|
||||||
|
|
||||||
CLI processing supplies two role clients from the same engine to the runner.
|
CLI processing supplies two role clients from the same engine to the runner.
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ library tests, then commit the focused configuration unit.
|
|||||||
|
|
||||||
## Stage 3 — Expose Compact Inheritance Evidence in Safe Inspection
|
## Stage 3 — Expose Compact Inheritance Evidence in Safe Inspection
|
||||||
|
|
||||||
|
**Completion:** Complete.
|
||||||
|
|
||||||
**Primary files:** `internal/framework/llm/safe_view.go`,
|
**Primary files:** `internal/framework/llm/safe_view.go`,
|
||||||
`internal/framework/llm/runtime_inspection_test.go`, `docs/cli.md`, and the
|
`internal/framework/llm/runtime_inspection_test.go`, `docs/cli.md`, and the
|
||||||
relevant explanation description in `docs/internal/llm-runtime.md` if needed.
|
relevant explanation description in `docs/internal/llm-runtime.md` if needed.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
ck "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rejectingTransport struct{ t *testing.T }
|
type rejectingTransport struct{ t *testing.T }
|
||||||
@@ -112,6 +113,76 @@ func TestOfflineProfileAssemblyAndSafeViews(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExplanationYAMLIncludesSafeInheritanceEvidence(t *testing.T) {
|
||||||
|
t.Setenv("SYNTHETIC_KEY", "synthetic-inherited-secret-never-print")
|
||||||
|
text := `version: 2
|
||||||
|
backends:
|
||||||
|
shared:
|
||||||
|
endpoint: https://shared.invalid/v1
|
||||||
|
concurrency: 3
|
||||||
|
queue_capacity: 0
|
||||||
|
credential: SYNTHETIC_KEY
|
||||||
|
offline:
|
||||||
|
extends: shared
|
||||||
|
concurrency: 1
|
||||||
|
llm:
|
||||||
|
proposal_profile: base
|
||||||
|
`
|
||||||
|
loaded, err := config.Load(ck.Bytes("config.yml", []byte(text)), "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
explanation, err := ExplanationYAML(loaded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(explanation), "synthetic-inherited-secret-never-print") {
|
||||||
|
t.Fatal("explanation exposed a resolved inherited credential")
|
||||||
|
}
|
||||||
|
var records []struct {
|
||||||
|
Path string `yaml:"path"`
|
||||||
|
Inheritance []struct {
|
||||||
|
From string `yaml:"from"`
|
||||||
|
To string `yaml:"to"`
|
||||||
|
Reference string `yaml:"reference"`
|
||||||
|
} `yaml:"inheritance"`
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(explanation, &records); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var endpoint, credential, concurrency *struct {
|
||||||
|
Path string `yaml:"path"`
|
||||||
|
Inheritance []struct {
|
||||||
|
From string `yaml:"from"`
|
||||||
|
To string `yaml:"to"`
|
||||||
|
Reference string `yaml:"reference"`
|
||||||
|
} `yaml:"inheritance"`
|
||||||
|
}
|
||||||
|
for i := range records {
|
||||||
|
switch records[i].Path {
|
||||||
|
case "/backends/offline/endpoint":
|
||||||
|
endpoint = &records[i]
|
||||||
|
case "/backends/offline/credential":
|
||||||
|
credential = &records[i]
|
||||||
|
case "/backends/offline/concurrency":
|
||||||
|
concurrency = &records[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endpoint == nil || len(endpoint.Inheritance) != 1 {
|
||||||
|
t.Fatalf("inherited endpoint evidence missing: %s", explanation)
|
||||||
|
}
|
||||||
|
step := endpoint.Inheritance[0]
|
||||||
|
if step.From != "/backends/shared/endpoint" || step.To != "/backends/offline/endpoint" || step.Reference != "/backends/offline/extends" {
|
||||||
|
t.Fatalf("inherited endpoint evidence: %+v", step)
|
||||||
|
}
|
||||||
|
if credential == nil || len(credential.Inheritance) != 1 {
|
||||||
|
t.Fatalf("inherited credential evidence missing: %s", explanation)
|
||||||
|
}
|
||||||
|
if concurrency == nil || len(concurrency.Inheritance) != 0 {
|
||||||
|
t.Fatalf("direct child override reported parent inheritance: %+v", concurrency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProfileAssemblyRejectsUnsafeSelections(t *testing.T) {
|
func TestProfileAssemblyRejectsUnsafeSelections(t *testing.T) {
|
||||||
t.Setenv("SYNTHETIC_KEY", "synthetic-value-never-print")
|
t.Setenv("SYNTHETIC_KEY", "synthetic-value-never-print")
|
||||||
dir := profileLayout(t, profileBase, "")
|
dir := profileLayout(t, profileBase, "")
|
||||||
|
|||||||
@@ -61,11 +61,17 @@ type derivationView struct {
|
|||||||
Reason string `yaml:"reason"`
|
Reason string `yaml:"reason"`
|
||||||
Dependencies []string `yaml:"dependencies,omitempty"`
|
Dependencies []string `yaml:"dependencies,omitempty"`
|
||||||
}
|
}
|
||||||
|
type inheritanceStepView struct {
|
||||||
|
From string `yaml:"from"`
|
||||||
|
To string `yaml:"to"`
|
||||||
|
Reference string `yaml:"reference"`
|
||||||
|
}
|
||||||
type explanationView struct {
|
type explanationView struct {
|
||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
Source string `yaml:"source"`
|
Source string `yaml:"source"`
|
||||||
Layer ck.Layer `yaml:"layer"`
|
Layer ck.Layer `yaml:"layer"`
|
||||||
Derivations []derivationView `yaml:"derivations,omitempty"`
|
Derivations []derivationView `yaml:"derivations,omitempty"`
|
||||||
|
Inheritance []inheritanceStepView `yaml:"inheritance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExplanationYAML reports only origins and recorded resolution operations.
|
// ExplanationYAML reports only origins and recorded resolution operations.
|
||||||
@@ -93,6 +99,11 @@ func ExplanationYAML(result *ck.Result[config.Config]) ([]byte, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := explanationView{Path: path.String(), Layer: info.Origin.Layer, Source: string(info.Origin.Layer)}
|
record := explanationView{Path: path.String(), Layer: info.Origin.Layer, Source: string(info.Origin.Layer)}
|
||||||
|
for _, step := range info.Origin.Inheritance {
|
||||||
|
record.Inheritance = append(record.Inheritance, inheritanceStepView{
|
||||||
|
From: step.From.String(), To: step.To.String(), Reference: step.Reference.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
if len(info.Origin.Locations) > 0 {
|
if len(info.Origin.Locations) > 0 {
|
||||||
if label := sources[info.Origin.Locations[0].SourceID]; label != "" {
|
if label := sources[info.Origin.Locations[0].SourceID]; label != "" {
|
||||||
record.Source = label
|
record.Source = label
|
||||||
|
|||||||
Reference in New Issue
Block a user