From aa7831589047d662e623ecc78e67493c6c6e0fb2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 13:05:18 +0000 Subject: [PATCH] fix(ogar-vocab): same-defect plural-table coverage for 4 more concepts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of compound-name promoted concepts revealed 4 more cases of the plural-table-alias defect codex flagged on PR #66 (wiki_pages, custom_fields, #67) and PR #68 (issue_relations, fixed on #68's branch): TimeEntry Rails table 'time_entries' lex 'time_entrie' BUG IssueStatus Rails table 'issue_statuses' lex 'issue_statuse' BUG IssuePriority Rails table 'issue_priorities' lex 'issue_prioritie' BUG Membership plural 'memberships' lex 'membership' BUG All four cases: ogar_codebook(plural_form) returned None because the single-s lexical fallback drops to a non-canonical string (Rails uses irregular pluralization — ies→y, s→ses — that single-char drop doesn't reverse). The promoted-arm only ran once on the full input, before lex, so the lex'd string never got re-matched. Fix: add the plural snake_case + lowercase-PascalCase forms to each arm: billable_work_entry: + 'timeentries' | 'time_entries' project_status: + 'issuestatuses' | 'issue_statuses' priority: + 'issuepriorities' | 'issue_priorities' project_membership: + 'membership' | 'memberships' Extended plural_table_aliases_resolve_to_promoted_codebook_id with 11 new entries (3 per concept where applicable) pinning each tableized variant -> codebook id. ogar-vocab 41 (test row count unchanged; entries inside extended); clippy clean; all 10 real-corpus tests still pass. --- crates/ogar-vocab/src/lib.rs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index f48751e..257ef10 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -1434,6 +1434,8 @@ pub fn canonical_concept(name: &str) -> String { lower.as_str(), "timeentry" | "time_entry" + | "timeentries" + | "time_entries" | "account.analytic.line" | "account_analytic_line" | "leistungsposition" @@ -1488,6 +1490,7 @@ pub fn canonical_concept(name: &str) -> String { if matches!( lower.as_str(), "issuestatus" | "issue_status" + | "issuestatuses" | "issue_statuses" | "status" | "statuses" | "project_status" | "projectstatus" ) { @@ -1505,7 +1508,8 @@ pub fn canonical_concept(name: &str) -> String { } // ── Project-mgmt promotions from the cross-curator overlap probe ── if matches!(lower.as_str(), - "member" | "members" | "project_membership" | "projectmembership" + "member" | "members" | "membership" | "memberships" + | "project_membership" | "projectmembership" ) { return "project_membership".to_string(); } @@ -1648,6 +1652,7 @@ pub fn canonical_concept(name: &str) -> String { if matches!( lower.as_str(), "issuepriority" | "issue_priority" + | "issuepriorities" | "issue_priorities" | "priority" | "priorities" ) { return "priority".to_string(); @@ -2844,21 +2849,34 @@ mod tests { #[test] fn plural_table_aliases_resolve_to_promoted_codebook_id() { for (singular, plural) in [ + // From PR #67 — original codex P2 fix. ("project_wiki_page", "wiki_pages"), ("project_wiki_page", "wikipages"), ("project_wiki_page", "WikiPages"), ("project_custom_field", "custom_fields"), ("project_custom_field", "customfields"), ("project_custom_field", "CustomFields"), - // Codex P2 on PR #68 — Redmine `issue_relations` is the - // Rails-tableized form of `IssueRelation`. The single-`s` - // lexical fallback drops to `issue_relation`, which is the - // singular curator alias but NOT a canonical concept name — - // so canonical_concept_id returned None. Adding the plural - // form to the promoted arm. + // From PR #68 — codex P2 on `issue_relations`. ("project_relation", "issue_relations"), ("project_relation", "issuerelations"), ("project_relation", "IssueRelations"), + // Proactive coverage for the same defect class on already-merged + // promoted concepts where the Rails-table plural is irregular + // (Rails: ies→y for repository/query, s→ses for status/priority, + // -ies/-ses pluralization in general). Single-`s` lexical + // fallback drops to non-canonical strings; explicit promotion + // ensures the codebook lookup hits. + ("billable_work_entry", "time_entries"), + ("billable_work_entry", "timeentries"), + ("billable_work_entry", "TimeEntries"), + ("project_status", "issue_statuses"), + ("project_status", "issuestatuses"), + ("project_status", "IssueStatuses"), + ("priority", "issue_priorities"), + ("priority", "issuepriorities"), + ("priority", "IssuePriorities"), + ("project_membership", "memberships"), + ("project_membership", "Memberships"), ] { let canonical_id = canonical_concept_id(singular); assert!(canonical_id.is_some(), "{singular} must be in codebook");