From 8509f938afbece7e63cf2b0a6dcf6f4f64b9acc3 Mon Sep 17 00:00:00 2001 From: iperev Date: Sat, 19 Sep 2026 17:35:30 +0200 Subject: [PATCH 1/2] fix: distinguish diagnostic fields from entity identities --- .../requirementsourcecodec/diagnostic_path.go | 7 +- .../requirementsourcecodec/path_roles_test.go | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 internal/kernel/requirementsourcecodec/path_roles_test.go diff --git a/internal/kernel/requirementsourcecodec/diagnostic_path.go b/internal/kernel/requirementsourcecodec/diagnostic_path.go index 174155e9..e30506c4 100644 --- a/internal/kernel/requirementsourcecodec/diagnostic_path.go +++ b/internal/kernel/requirementsourcecodec/diagnostic_path.go @@ -15,6 +15,10 @@ func resolveModelPath(wire document, path string) diagnosticPath { if len(segments) == 0 { return diagnosticPath{} } + // Only the model's quoted-identity syntax denotes an entity lookup. + if !strings.HasPrefix(path, segments[0]+`["`) { + return conventionalModelPath(segments) + } switch segments[0] { case "requirements": return resolveRequirementPath(wire, segments) @@ -102,9 +106,6 @@ func resolveIdentifiedPath(segments []string, root string, count int, identity f if len(segments) < 2 { return conventionalModelPath(segments) } - if index, err := strconv.Atoi(segments[1]); err == nil && index >= 0 && index < count { - return appendSafeSegments(pointer(root, index), segments[2:]) - } for index := 0; index < count; index++ { if identity(index) == segments[1] { return appendSafeSegments(pointer(root, index), segments[2:]) diff --git a/internal/kernel/requirementsourcecodec/path_roles_test.go b/internal/kernel/requirementsourcecodec/path_roles_test.go new file mode 100644 index 00000000..043c8e84 --- /dev/null +++ b/internal/kernel/requirementsourcecodec/path_roles_test.go @@ -0,0 +1,99 @@ +package requirementsourcecodec + +import ( + "bytes" + "encoding/json" + "fmt" + "slices" + "testing" + + "github.com/research-engineering/agentic-proofkit/internal/kernel/requirementsourcemodel" +) + +func TestScenarioDiagnosticRolesDoNotCollide(t *testing.T) { + for _, reverse := range []bool{false, true} { + for _, role := range []string{"aggregate", "position", "identity"} { + t.Run(fmt.Sprintf("%s/reverse=%t", role, reverse), func(t *testing.T) { + payload := mutateRoot(t, mustPayload(t), func(root map[string]any) { + first := root["scenarios"].([]any)[0].(map[string]any) + first["scenarioId"] = "examples" + second := make(map[string]any, len(first)) + for key, value := range first { + second[key] = value + } + second["scenarioId"] = "other" + values := []any{first, second} + if reverse { + slices.Reverse(values) + } + root["scenarios"] = values + }) + if _, err := Parse(payload); err != nil { + t.Fatalf("positive control: %v", err) + } + limits := requirementsourcemodel.DefaultLimits() + path, code := "/scenarios/examples", "example_budget_exceeded" + var object map[string]json.RawMessage + if err := json.Unmarshal(payload, &object); err != nil { + t.Fatal(err) + } + expected := object["scenarios"] + switch role { + case "aggregate": + limits.MaxExamples = 3 + case "position": + limits.MaxExamplesPerScenario = 1 + path, code = "/scenarios/0/examples", "collection_limit_exceeded" + var scenarios []map[string]json.RawMessage + if err := json.Unmarshal(expected, &scenarios); err != nil { + t.Fatal(err) + } + expected = scenarios[0]["examples"] + case "identity": + index := 0 + if reverse { + index = 1 + } + payload = mutateRoot(t, payload, func(root map[string]any) { + root["scenarios"].([]any)[index].(map[string]any)["nonClaimRefs"] = []any{"NCL-MISSING"} + }) + path, code = fmt.Sprintf("/scenarios/%d/nonClaimRefs", index), "dangling_nonclaim_ref" + expected = []byte(`["NCL-MISSING"]`) + } + _, err := ParseWithLimits(payload, DefaultLimits(), limits) + assertDiagnostic(t, err, code, path) + span := err.(*Error).Diagnostic().Span + if !bytes.Equal(payload[span.Start:span.End], expected) { + t.Fatal("diagnostic role selected another original value") + } + }) + } + } +} + +func TestModelPathRoleSelectsOnlyQuotedIdentities(t *testing.T) { + // Deliberately ambiguous spellings isolate the private route protocol; + // this wire fixture does not claim model admission for every root. + wire := document{ + Profiles: []profile{{ProfileID: "examples"}, {ProfileID: "0"}}, + NonClaimDefinitions: []nonClaimDefinition{{NonClaimID: "examples"}, {NonClaimID: "0"}}, + Vocabulary: []vocabularyTerm{{TermID: "examples"}, {TermID: "0"}}, + Scenarios: []scenario{{ScenarioID: "examples"}, {ScenarioID: "0"}}, + Derivations: []derivation{{DerivationID: "examples"}, {DerivationID: "0"}}, + } + for _, root := range []string{"profiles", "nonClaimDefinitions", "vocabulary", "scenarios", "derivations"} { + for _, item := range []struct{ suffix, want string }{ + {"", ""}, {".examples", "/examples"}, {"[0].examples", "/0/examples"}, + {`["examples"].nonClaimRefs`, "/0/nonClaimRefs"}, + {`["0"].nonClaimRefs`, "/1/nonClaimRefs"}, + } { + t.Run(root+item.suffix, func(t *testing.T) { + got := resolveModelPath(wire, root+item.suffix) + want := "/" + root + item.want + if got.lookup != want || got.reported != want { + t.Fatalf("resolved %#v, want %s", got, want) + } + }) + } + } +} From 2b081df8314c74f2b5a497f255dc1f26918670a6 Mon Sep 17 00:00:00 2001 From: iperev Date: Sat, 19 Sep 2026 17:51:09 +0200 Subject: [PATCH 2/2] test: bind diagnostic assertions to exact source occurrences --- .../requirementsourcecodec/path_roles_test.go | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/internal/kernel/requirementsourcecodec/path_roles_test.go b/internal/kernel/requirementsourcecodec/path_roles_test.go index 043c8e84..de376102 100644 --- a/internal/kernel/requirementsourcecodec/path_roles_test.go +++ b/internal/kernel/requirementsourcecodec/path_roles_test.go @@ -12,7 +12,7 @@ import ( func TestScenarioDiagnosticRolesDoNotCollide(t *testing.T) { for _, reverse := range []bool{false, true} { - for _, role := range []string{"aggregate", "position", "identity"} { + for _, role := range []string{"aggregate", "structural-position", "position", "identity"} { t.Run(fmt.Sprintf("%s/reverse=%t", role, reverse), func(t *testing.T) { payload := mutateRoot(t, mustPayload(t), func(root map[string]any) { first := root["scenarios"].([]any)[0].(map[string]any) @@ -38,10 +38,11 @@ func TestScenarioDiagnosticRolesDoNotCollide(t *testing.T) { t.Fatal(err) } expected := object["scenarios"] + occurrences := 1 switch role { case "aggregate": limits.MaxExamples = 3 - case "position": + case "structural-position": limits.MaxExamplesPerScenario = 1 path, code = "/scenarios/0/examples", "collection_limit_exceeded" var scenarios []map[string]json.RawMessage @@ -49,22 +50,37 @@ func TestScenarioDiagnosticRolesDoNotCollide(t *testing.T) { t.Fatal(err) } expected = scenarios[0]["examples"] - case "identity": + occurrences = 2 + case "position", "identity": index := 0 if reverse { index = 1 } payload = mutateRoot(t, payload, func(root map[string]any) { - root["scenarios"].([]any)[index].(map[string]any)["nonClaimRefs"] = []any{"NCL-MISSING"} + scenario := root["scenarios"].([]any)[index].(map[string]any) + if role == "position" { + scenario["actionSequence"] = []any{"FIXME position sentinel"} + } else { + scenario["nonClaimRefs"] = []any{"NCL-MISSING"} + } }) path, code = fmt.Sprintf("/scenarios/%d/nonClaimRefs", index), "dangling_nonclaim_ref" expected = []byte(`["NCL-MISSING"]`) + if role == "position" { + path, code = fmt.Sprintf("/scenarios/%d/actionSequence/0", index), "placeholder_text" + expected = []byte(`"FIXME position sentinel"`) + } } _, err := ParseWithLimits(payload, DefaultLimits(), limits) assertDiagnostic(t, err, code, path) span := err.(*Error).Diagnostic().Span - if !bytes.Equal(payload[span.Start:span.End], expected) { - t.Fatal("diagnostic role selected another original value") + if bytes.Count(payload, expected) != occurrences { + t.Fatal("fixture occurrence count differs") + } + start := bytes.Index(payload, expected) + want := ByteSpan{Start: int64(start), End: int64(start + len(expected))} + if span != want { + t.Fatalf("diagnostic span = %#v, want original occurrence %#v", span, want) } }) } @@ -83,7 +99,7 @@ func TestModelPathRoleSelectsOnlyQuotedIdentities(t *testing.T) { } for _, root := range []string{"profiles", "nonClaimDefinitions", "vocabulary", "scenarios", "derivations"} { for _, item := range []struct{ suffix, want string }{ - {"", ""}, {".examples", "/examples"}, {"[0].examples", "/0/examples"}, + {"", ""}, {".examples", "/examples"}, {"[0].examples", "/0/examples"}, {"[1].examples", "/1/examples"}, {`["examples"].nonClaimRefs`, "/0/nonClaimRefs"}, {`["0"].nonClaimRefs`, "/1/nonClaimRefs"}, } {