Skip to content

Commit fea4dfd

Browse files
authored
Merge pull request #113 from AdaWorldAPI/claude/openproject-alias-member
feat(ogar-vocab): add OpenProject `Member` alias for project_membership
2 parents 4b88f8e + f292305 commit fea4dfd

1 file changed

Lines changed: 49 additions & 6 deletions

File tree

crates/ogar-vocab/src/ports.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ pub const OPENPROJECT_ALIASES: &[(&str, u16)] = &[
139139
// class name. Match the actual class so `class_id("IssuePriority")`
140140
// resolves.
141141
("IssuePriority", class_ids::PRIORITY),
142+
// OpenProject's actual Rails class for `project_membership` is `Member`
143+
// (mirrors Redmine — both forks ship the join row as `Member`). The
144+
// engine-walking corpus snapshot in op-canon carries `Member`. The
145+
// earlier `Membership` alias was pre-snapshot prose; keep it as a
146+
// deprecated synonym so any consumer holding the old name still
147+
// resolves, but `Member` is the canonical OP surface for the concept.
148+
// Closes the openproject-nexgen-rs#56 pinned
149+
// `port_and_snapshot_membership_vocab_mismatch_is_known` test.
150+
("Member", class_ids::PROJECT_MEMBERSHIP),
142151
("Membership", class_ids::PROJECT_MEMBERSHIP),
143152
("Journal", class_ids::PROJECT_JOURNAL),
144153
("Repository", class_ids::PROJECT_REPOSITORY),
@@ -590,7 +599,10 @@ mod tests {
590599
("Status", "IssueStatus", class_ids::PROJECT_STATUS),
591600
("Type", "Tracker", class_ids::PROJECT_TYPE),
592601
("IssuePriority", "IssuePriority", class_ids::PRIORITY),
593-
("Membership", "Member", class_ids::PROJECT_MEMBERSHIP),
602+
// Both forks ship the membership join as `Member` (engine-walking
603+
// corpus snapshot). The OpenProject port still carries the legacy
604+
// `Membership` synonym; the canonical pair is now Member ↔ Member.
605+
("Member", "Member", class_ids::PROJECT_MEMBERSHIP),
594606
("Journal", "Journal", class_ids::PROJECT_JOURNAL),
595607
("Repository", "Repository", class_ids::PROJECT_REPOSITORY),
596608
("Version", "Version", class_ids::PROJECT_VERSION),
@@ -641,6 +653,34 @@ mod tests {
641653
}
642654
}
643655

656+
/// OpenProject ships the membership join as `Member` (mirrors Redmine —
657+
/// both engine-walking corpus snapshots carry that name). The earlier
658+
/// `Membership` surface stays as a deprecated synonym so any consumer
659+
/// holding the old name still resolves; this test pins both routes to
660+
/// the same canonical id so the additive contract can't drift.
661+
///
662+
/// Closes the openproject-nexgen-rs#56 pinned
663+
/// `port_and_snapshot_membership_vocab_mismatch_is_known` test — once
664+
/// this lands and op-canon bumps its `ogar-vocab` git pin,
665+
/// `OpenProjectPort::class_id("Member")` flips from `None` to
666+
/// `Some(PROJECT_MEMBERSHIP)`, that pin self-fails, and the consumer
667+
/// drops it.
668+
#[test]
669+
fn openproject_member_and_membership_both_resolve_to_project_membership() {
670+
let target = Some(class_ids::PROJECT_MEMBERSHIP);
671+
// Canonical surface (matches the OpenProject corpus + Redmine):
672+
assert_eq!(OpenProjectPort::class_id("Member"), target);
673+
// Deprecated synonym kept for backward compatibility:
674+
assert_eq!(OpenProjectPort::class_id("Membership"), target);
675+
// Both ports converge under the same canonical surface name now:
676+
assert_eq!(RedminePort::class_id("Member"), target);
677+
assert_eq!(
678+
OpenProjectPort::class_id("Member"),
679+
RedminePort::class_id("Member"),
680+
"OP `Member` and RM `Member` must converge on the same id",
681+
);
682+
}
683+
644684
#[test]
645685
fn unknown_public_names_resolve_to_none() {
646686
assert_eq!(OpenProjectPort::class_id("NotAConcept"), None);
@@ -808,20 +848,23 @@ mod tests {
808848
// classes its corpus ships, no phantom aliases for concepts
809849
// the port doesn't expose as a top-level model.
810850
//
811-
// OpenProject (27): 25 distinct concept entries + 2 STI-fold
851+
// OpenProject (28): 25 distinct concept entries + 2 STI-fold
812852
// rows (Principal, Group fold into PROJECT_ACTOR alongside
813-
// User). No `Comment` entry — OpenProject's Journal carries
814-
// the comment-equivalent state, no standalone Comment model.
853+
// User) + 1 deprecated synonym row (Membership → Member; both
854+
// resolve to PROJECT_MEMBERSHIP, the canonical surface is
855+
// Member per the engine-walking corpus snapshot). No `Comment`
856+
// entry — OpenProject's Journal carries the comment-equivalent
857+
// state, no standalone Comment model.
815858
// Redmine (28): 26 distinct concept entries + 2 STI-fold rows.
816859
// Has a standalone `Comment` model on top of `Journal` (the
817-
// one extra row vs OpenProject).
860+
// one extra row vs OpenProject's canonical concepts).
818861
//
819862
// Both gained the same +2 STI-fold rows and +0/+1 IssuePriority
820863
// entry under codex P2 on PR #87 (Redmine previously had no
821864
// priority entry; OpenProject's was misnamed `Priority`).
822865
assert_eq!(
823866
OpenProjectPort::aliases().len(),
824-
27,
867+
28,
825868
"OpenProject alias count drift — re-count the table"
826869
);
827870
assert_eq!(

0 commit comments

Comments
 (0)