diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index e166a04..4181b67 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -2,7 +2,7 @@ **Updated:** 2026-11-11 -**Status:** 8 of nine findings resolved. Findings refer to implementation revision +**Status:** All nine findings resolved. Findings refer to implementation revision `aa8073c`. See the [implementation plan](implementation.md). ## Review Result @@ -295,6 +295,8 @@ of identity loss over capitalization and the existing rejection rules. ### R9 — Name the Affected Protected Alias (P3) +**Completed.** Protection feedback identifies a spelling whose occurrence count fell. The alias regression and existing spoken-word protection tests pass. + **Affected plan:** Stage 5, item 4. **Evidence:** When an identity loses a mention, the @@ -352,6 +354,6 @@ the unchanged spelling. - Fresh tests passed for runner, validators, process reporting, LLM adapter, preflight, response schemas, proposal generation, configuration, and prompts during the algorithmic audit. - -Resolve the findings with focused checks at their owning boundaries. Retain the -current architecture and accepted operator choices. +- After remediation, the fresh full test suite, targeted race checks for the + runner, validators, process reporting, LLM adapter, and CLI, and the CLI build + passed. diff --git a/internal/framework/validators/protected_terms.go b/internal/framework/validators/protected_terms.go index d9ffd0f..82b2bb7 100644 --- a/internal/framework/validators/protected_terms.go +++ b/internal/framework/validators/protected_terms.go @@ -99,8 +99,23 @@ func validateIdentityPreservation(beforeByID, afterByID map[glossary.Identity][] var selected string found := false for identity, beforeItems := range beforeByID { - if len(afterByID[identity]) < len(beforeItems) && len(beforeItems) > 0 { - if spelling := beforeItems[0].canonical; !found || spelling < selected { + afterItems := afterByID[identity] + if len(afterItems) >= len(beforeItems) { + continue + } + // Protect identity counts. Within an identity that lost a mention, + // use spelling counts to identify which alias was affected. + remaining := make(map[string]int) + for _, item := range afterItems { + remaining[item.canonical]++ + } + for _, item := range beforeItems { + spelling := item.canonical + if remaining[spelling] > 0 { + remaining[spelling]-- + continue + } + if !found || spelling < selected { selected, found = spelling, true } } diff --git a/internal/framework/validators/protected_terms_test.go b/internal/framework/validators/protected_terms_test.go index 27cdfdf..09a5862 100644 --- a/internal/framework/validators/protected_terms_test.go +++ b/internal/framework/validators/protected_terms_test.go @@ -3,6 +3,7 @@ package validators import ( "reflect" "sort" + "strings" "testing" domain "gitea.maximumdirect.net/eric/audita/v3/internal/core/glossary" @@ -107,3 +108,28 @@ func TestProtectedNameRepetitionRequiresSurvivingIdentityAndSpokenWordScope(t *t }) } } + +func TestProtectedNameFindingIdentifiesRemovedAlias(t *testing.T) { + req := Request{ + ModuleKey: "glossary", + IndependentEvidence: true, + ReplacementPolicy: proposals.ReplacementPolicyReplaceAll, + WorkingTranscript: &schema.Transcript{Segments: []schema.Segment{ + {ID: 1, Text: "Mira spoke to Captain Mira"}, + }}, + ProtectedTerms: domain.Vocabulary([]domain.Entry{{ID: "mira", Name: "Mira", Aliases: []string{"Captain Mira"}}}, nil), + CandidateProposal: []proposals.EnrichedCorrectionProposal{{ + CorrectionProposal: proposals.CorrectionProposal{ + TargetSegmentID: 1, OriginalText: "Captain Mira", CorrectedText: "the captain", Confidence: 0.9, + }, + }}, + } + result, err := ValidateProtectedTerms(req) + if err != nil || len(result.Decisions) != 1 { + t.Fatalf("result=%+v err=%v", result, err) + } + decision := result.Decisions[0] + if decision.Approved || decision.ReasonCode != ReasonProtectedGlossaryTerm || !strings.Contains(decision.Message, `"Captain Mira"`) { + t.Fatalf("finding did not identify the removed alias: %+v", decision) + } +}