@@ -19,7 +19,6 @@ import (
1919 stderrors "errors"
2020 "os"
2121 "sort"
22- "strings"
2322 "testing"
2423
2524 aicrerrors "github.com/NVIDIA/aicr/pkg/errors"
@@ -30,9 +29,13 @@ const goldenPath = "testdata/coverage_golden.yaml"
3029
3130// coverageClassification is the golden outcome for one projected query.
3231type coverageClassification struct {
33- Outcome string `yaml:"outcome"` // "success" | "error"
34- Uncovered []string `yaml:"uncovered,omitempty"` // coverage errors
35- RequiresOS bool `yaml:"requiresOS,omitempty"` // requireOSIfNeeded guard errors
32+ Outcome string `yaml:"outcome"` // "success" | "error"
33+ Uncovered []string `yaml:"uncovered,omitempty"` // completeness failures
34+ // StrictDimensions names the dimensions a joint-sufficiency failure
35+ // demanded, read from the error's structured context rather than its
36+ // message text so rewording the message cannot silently reclassify a
37+ // projection and retire the golden matrix's guard on the rule.
38+ StrictDimensions []string `yaml:"strictDimensions,omitempty"`
3639 // ValidCompletions pins the completion-suggestion content per uncovered
3740 // dimension (canonical tupleKey strings, in minimalTuples order) so a
3841 // regression in the suggestion machinery on the real catalog flips a
@@ -112,7 +115,8 @@ func TestCoverageGoldenMatrix(t *testing.T) {
112115 continue
113116 }
114117 if w .Outcome != got [k ].Outcome || ! equalStrings (w .Uncovered , got [k ].Uncovered ) ||
115- w .RequiresOS != got [k ].RequiresOS || ! equalCompletions (w .ValidCompletions , got [k ].ValidCompletions ) {
118+ ! equalStrings (w .StrictDimensions , got [k ].StrictDimensions ) ||
119+ ! equalCompletions (w .ValidCompletions , got [k ].ValidCompletions ) {
116120
117121 t .Errorf ("projection %q flipped: golden %+v, now %+v" , k , w , got [k ])
118122 }
@@ -151,10 +155,8 @@ func classify(ctx context.Context, t *testing.T, store *MetadataStore, q *Criter
151155 if err == nil {
152156 return coverageClassification {Outcome : "success" }
153157 }
154- msg := err .Error ()
155- if strings .Contains (msg , "; specify " ) {
156- // Joint-sufficiency failure (formerly the requireOSIfNeeded guard).
157- return coverageClassification {Outcome : "error" , RequiresOS : true }
158+ if strict := strictDimensionsFromError (err ); len (strict ) > 0 {
159+ return coverageClassification {Outcome : "error" , StrictDimensions : strict }
158160 }
159161 uncovered , completions := coverageDetailsFromError (err )
160162 if len (uncovered ) == 0 {
@@ -163,6 +165,28 @@ func classify(ctx context.Context, t *testing.T, store *MetadataStore, q *Criter
163165 return coverageClassification {Outcome : "error" , Uncovered : uncovered , ValidCompletions : completions }
164166}
165167
168+ // strictDimensionsFromError extracts the dimension names a joint-sufficiency
169+ // failure demanded, from the error's `strictDimensions` context, or nil when
170+ // err is not one. Reading the structured context rather than the message keeps
171+ // the golden matrix pinned to the rule instead of to its phrasing.
172+ func strictDimensionsFromError (err error ) []string {
173+ var se * aicrerrors.StructuredError
174+ if ! stderrors .As (err , & se ) || se .Context == nil {
175+ return nil
176+ }
177+ entries , ok := se .Context ["strictDimensions" ].([]map [string ]any )
178+ if ! ok {
179+ return nil
180+ }
181+ names := make ([]string , 0 , len (entries ))
182+ for _ , entry := range entries {
183+ if name , ok := entry ["dimension" ].(string ); ok {
184+ names = append (names , name )
185+ }
186+ }
187+ return names
188+ }
189+
166190// coverageDetailsFromError extracts the uncovered dimension names and their
167191// completion suggestions (as canonical tupleKey strings, preserving
168192// minimalTuples order) from a StructuredError's "uncovered" context entries,
0 commit comments