feat(ogar-vocab): ProjectRelation + ProjectChangeset + ProjectWatcher (3 promotions)#68
Conversation
… (3 promotions) Closes the last project_work_item forward reference (.relations) and lands two natural neighbours that pair with already-canonical concepts: | Canonical | id | Redmine | OpenProject | |----------------------|---------|----------------|-------------| | project_relation | 0x0111 | IssueRelation | Relation | | project_changeset | 0x0112 | Changeset | Changeset | | project_watcher | 0x0113 | Watcher | Watcher | project_relation has cross-curator name divergence (Redmine 'IssueRelation' vs OP 'Relation') — same shape conceptually (directed work-item edge with from/to/type/delay-or-lag). Canonical chooses 'lag' (project- scheduling term) over Redmine's 'delay'. project_changeset pairs with project_repository (already canonical, #66): both have belongs_to :repository + belongs_to :user (the committer maps to project_actor) plus revision/comments/commit_date. project_watcher captures the polymorphic follow-relationship both curators ship with belongs_to :user + belongs_to :watchable, polymorphic. The polymorphic target stays opaque at the canonical layer; the watchable_type discriminator is the curator-local cell. - CODEBOOK entries: 0x0111-0x0113 in the project-mgmt block. - Resolver arms covering each curator name + PascalCase + plural forms (singular/plural/snake_case/PascalCase per the #66 + #67 doctrine). - 3 canonical class functions with Language::Unknown + typed attrs + obvious family edges. - Extended codebook_ids_are_domain_prefixed_and_consistent test to cover the 3 new concepts. - Extended project_mgmt_batch_promotions_each_have_a_codebook_id_and_shape with 3 new entries. - Extended project_mgmt_resolver_arms_collapse_curator_names with the alias coverage (including IssueRelation/Relation divergence). - New real-corpus test: redmine_and_openproject_relation_changeset_watcher_converge — iterates all 6 extracted classes (Redmine IssueRelation + Changeset + Watcher, OP Relation + Changeset + Watcher) and asserts canonical_concept + canonical_id match. ogar-vocab 41, ogar-from-ruff 24, real-corpus 11 (+1); clippy clean. Project-mgmt block (0x01XX) is now 19 concepts. project_work_item family edges all have canonical class targets.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 36e0b144ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "issuerelation" | "issue_relation" | ||
| | "relation" | "relations" | ||
| | "project_relation" | "projectrelation" |
There was a problem hiding this comment.
Handle Redmine relation table aliases
Please add the tableized Redmine plural (issue_relations, and likely IssueRelations) to this resolver arm. ogar_codebook calls canonical_concept only once, so issue_relations currently falls through to the lexical fallback as issue_relation, which is not in CODEBOOK, and downstream consumers that resolve Rails table labels get None instead of the new project_relation id; this is the same plural-table alias case already guarded for other promoted concepts in plural_table_aliases_resolve_to_promoted_codebook_id.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed and fixed directly on this branch (force-pushed ebe7631). Added "issuerelations" | "issue_relations" to the project_relation promoted arm — same pattern as #67 (wiki_pages / custom_fields). The plural_table_aliases test gained 3 new entries (issue_relations, issuerelations, IssueRelations → project_relation codebook id).
Also did a proactive audit and found the same defect class on 4 more already-merged concepts (Rails irregular pluralization the single-s lex can't reverse): time_entries, issue_statuses, issue_priorities, memberships. Those are landed in #69 (separate PR off main since they touch merged code). Thanks for the catch.
Codex flagged that ogar_codebook('issue_relations') returned None: the
promoted arm only matched the singular 'issue_relation', so the Rails-
tableized plural fell through to lexical, which drops the trailing s and
returns 'issue_relation' — the singular curator alias, NOT a canonical
concept name. canonical_concept_id then misses on the curator alias.
Same defect class as #67 (wiki_pages, custom_fields). Fix is the same:
add the plural snake_case + lowercase-PascalCase forms to the promoted
arm so they short-circuit before lexical:
'issuerelations' | 'issue_relations'
The plural_table_aliases_resolve_to_promoted_codebook_id test gains 3
entries pinning the contract: issue_relations / issuerelations /
IssueRelations all map to project_relation codebook id.
ogar-vocab 41, clippy clean.
(Note: same defect exists on 4 other compound concepts —
time_entries / issue_statuses / issue_priorities / memberships —
fixed in a follow-up PR off main since they touch already-merged
canonical classes.)
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.
3 more promotions — closes the last
project_work_itemforward refproject_relation0x0111IssueRelationRelationproject_changeset0x0112ChangesetChangesetproject_watcher0x0113WatcherWatcherproject_relationhas cross-curator name divergence (RedmineIssueRelationvs OPRelation) but identical shape conceptually — directed work-item edge withfrom/to/relation_type/delay-or-lag. Canonical choselag(project-scheduling standard) over Redmine'sdelay.project_changesetpairs withproject_repository(merged via #66): both havebelongs_to :repository+belongs_to :user(committer →project_actor) plusrevision/comments/commit_date.project_watcheris the polymorphic follow-relationship both ship withbelongs_to :user+belongs_to :watchable, polymorphic: true. The polymorphic target stays opaque at the canonical layer.What
0x0111–0x0113in the project-mgmt block.Language::Unknown+ typed attrs + obvious family edges.codebook_ids_are_domain_prefixed_and_consistent,project_mgmt_batch_promotions_each_have_a_codebook_id_and_shape, andproject_mgmt_resolver_arms_collapse_curator_namesto cover the 3 new concepts.redmine_and_openproject_relation_changeset_watcher_converge— iterates all 6 extracted classes and asserts canonical_concept + canonical_id match.Green
ogar-vocab41 ·ogar-from-ruff24 ·ogar-from-rails11 real-corpus (+1) · clippy clean.Project-mgmt block (
0x01XX) now 19 conceptsAll
project_work_itemfamily edges (project,status,type,priority,author/assigneeviaproject_actor,journals,relations,time_entriesviabillable_work_entry) point at canonical classes that exist. The ladder for OP↔Redmine is at saturation forproject_work_item's immediate neighbourhood.🤖 Generated with Claude Code