|
| 1 | +//! `bridges::codebook` — shared canonical class_id constants for the |
| 2 | +//! ports that emit through the OGAR codebook. |
| 3 | +//! |
| 4 | +//! Northstar plan §3 C4/C5 follow-up. Codex P1 on PR #559 flagged that |
| 5 | +//! `OpenProjectBridge::entity("WorkPackage")` and |
| 6 | +//! `RedmineBridge::entity("Issue")` minted **distinct** entity_type_ids |
| 7 | +//! despite the PR body promising both routed to the same |
| 8 | +//! `project_work_item` arm. Root cause: the registry's |
| 9 | +//! [`crate::registry::RegistryState::append`] reuses `entity_type_id` |
| 10 | +//! only when the exact `ogit_uri` matches, so per-port public names |
| 11 | +//! never converge on a shared id by accident. |
| 12 | +//! |
| 13 | +//! This module is the single source of truth for the canonical class_ids; |
| 14 | +//! both [`crate::bridges::OpenProjectBridge`] and |
| 15 | +//! [`crate::bridges::RedmineBridge`] reference these constants in their |
| 16 | +//! per-port public-name → class_id alias tables. Consumers that dispatch |
| 17 | +//! on `EntityRef::schema_ptr.entity_type_id()` get the same id for |
| 18 | +//! `WorkPackage`/`Issue`/etc. across both bridges — the cross-fork |
| 19 | +//! convergence the codebook was calcified for. |
| 20 | +//! |
| 21 | +//! # Source of authority |
| 22 | +//! |
| 23 | +//! Mirrors `ogar-vocab::class_ids::*` (the calcified canonical codebook, |
| 24 | +//! 32 promoted concepts). This crate intentionally **does not** depend |
| 25 | +//! on `ogar-vocab` to avoid a cycle (OGAR depends on |
| 26 | +//! lance-graph-contract, and lance-graph-ontology sits between them); |
| 27 | +//! the constants below are the same values verbatim. Adding a |
| 28 | +//! cross-crate `class_ids_match_ogar_vocab` smoke test in OGAR is a |
| 29 | +//! roadmap item. |
| 30 | +//! |
| 31 | +//! # Coverage |
| 32 | +//! |
| 33 | +//! Project-management arm (0x01XX) — all 26 promoted concepts. |
| 34 | +//! Commerce arm (0x02XX) and other arms — added as ports adopt them. |
| 35 | +
|
| 36 | +// ── Project-management arm (0x01XX) — 26 promoted concepts ──────────── |
| 37 | + |
| 38 | +/// `project` (0x0101) — the project itself. OpenProject `Project`, |
| 39 | +/// Redmine `Project`. |
| 40 | +pub const PROJECT: u16 = 0x0101; |
| 41 | + |
| 42 | +/// `project_work_item` (0x0102) — the canonical work-item concept. |
| 43 | +/// **OpenProject `WorkPackage`, Redmine `Issue` — the headline |
| 44 | +/// convergence pin.** |
| 45 | +pub const PROJECT_WORK_ITEM: u16 = 0x0102; |
| 46 | + |
| 47 | +/// `billable_work_entry` (0x0103) — hours logged against a work item. |
| 48 | +/// OpenProject `TimeEntry`, Redmine `TimeEntry`. |
| 49 | +pub const BILLABLE_WORK_ENTRY: u16 = 0x0103; |
| 50 | + |
| 51 | +/// `project_actor` (0x0104) — User / Principal in the port's terms. |
| 52 | +pub const PROJECT_ACTOR: u16 = 0x0104; |
| 53 | + |
| 54 | +/// `project_status` (0x0105) — OpenProject `Status`, Redmine |
| 55 | +/// `IssueStatus`. |
| 56 | +pub const PROJECT_STATUS: u16 = 0x0105; |
| 57 | + |
| 58 | +/// `project_type` (0x0106) — OpenProject `Type`, Redmine `Tracker`. |
| 59 | +pub const PROJECT_TYPE: u16 = 0x0106; |
| 60 | + |
| 61 | +/// `priority` (0x0107). |
| 62 | +pub const PRIORITY: u16 = 0x0107; |
| 63 | + |
| 64 | +/// `project_membership` (0x0108) — OpenProject `Membership`, Redmine |
| 65 | +/// `Member`. |
| 66 | +pub const PROJECT_MEMBERSHIP: u16 = 0x0108; |
| 67 | + |
| 68 | +/// `project_journal` (0x0109) — change history. |
| 69 | +pub const PROJECT_JOURNAL: u16 = 0x0109; |
| 70 | + |
| 71 | +/// `project_repository` (0x010A) — SCM repository attached to a project. |
| 72 | +pub const PROJECT_REPOSITORY: u16 = 0x010A; |
| 73 | + |
| 74 | +/// `project_version` (0x010B) — milestone / target version. |
| 75 | +pub const PROJECT_VERSION: u16 = 0x010B; |
| 76 | + |
| 77 | +/// `project_wiki_page` (0x010C). |
| 78 | +pub const PROJECT_WIKI_PAGE: u16 = 0x010C; |
| 79 | + |
| 80 | +/// `project_query` (0x010D) — saved filter / list-view spec. |
| 81 | +pub const PROJECT_QUERY: u16 = 0x010D; |
| 82 | + |
| 83 | +/// `project_attachment` (0x010E). |
| 84 | +pub const PROJECT_ATTACHMENT: u16 = 0x010E; |
| 85 | + |
| 86 | +/// `project_comment` (0x010F). |
| 87 | +pub const PROJECT_COMMENT: u16 = 0x010F; |
| 88 | + |
| 89 | +/// `project_custom_field` (0x0110). |
| 90 | +pub const PROJECT_CUSTOM_FIELD: u16 = 0x0110; |
| 91 | + |
| 92 | +/// `project_relation` (0x0111) — OpenProject `Relation`, Redmine |
| 93 | +/// `IssueRelation`. |
| 94 | +pub const PROJECT_RELATION: u16 = 0x0111; |
| 95 | + |
| 96 | +/// `project_changeset` (0x0112). |
| 97 | +pub const PROJECT_CHANGESET: u16 = 0x0112; |
| 98 | + |
| 99 | +/// `project_watcher` (0x0113). |
| 100 | +pub const PROJECT_WATCHER: u16 = 0x0113; |
| 101 | + |
| 102 | +/// `project_news` (0x0114). |
| 103 | +pub const PROJECT_NEWS: u16 = 0x0114; |
| 104 | + |
| 105 | +/// `project_message` (0x0115) — forum message. |
| 106 | +pub const PROJECT_MESSAGE: u16 = 0x0115; |
| 107 | + |
| 108 | +/// `project_forum` (0x0116) — OpenProject `Forum`, Redmine `Board`. |
| 109 | +pub const PROJECT_FORUM: u16 = 0x0116; |
| 110 | + |
| 111 | +/// `project_role` (0x0117). |
| 112 | +pub const PROJECT_ROLE: u16 = 0x0117; |
| 113 | + |
| 114 | +/// `project_member_role` (0x0118) — join row between Membership and Role. |
| 115 | +pub const PROJECT_MEMBER_ROLE: u16 = 0x0118; |
| 116 | + |
| 117 | +/// `project_custom_value` (0x0119) — instance of a CustomField for one |
| 118 | +/// work item. |
| 119 | +pub const PROJECT_CUSTOM_VALUE: u16 = 0x0119; |
| 120 | + |
| 121 | +/// `project_enabled_module` (0x011A) — per-project module toggle. |
| 122 | +pub const PROJECT_ENABLED_MODULE: u16 = 0x011A; |
| 123 | + |
| 124 | +#[cfg(test)] |
| 125 | +mod tests { |
| 126 | + use super::*; |
| 127 | + |
| 128 | + /// Headline convergence pin: the canonical concept both |
| 129 | + /// OpenProject's `WorkPackage` and Redmine's `Issue` route through |
| 130 | + /// has the codebook id `0x0102`. The whole point of C4 + C5. |
| 131 | + #[test] |
| 132 | + fn project_work_item_is_0x0102() { |
| 133 | + assert_eq!(PROJECT_WORK_ITEM, 0x0102); |
| 134 | + } |
| 135 | + |
| 136 | + /// All 26 project-management arm ids are in the `0x01XX` block and |
| 137 | + /// strictly monotonic in the order they appear here — matches the |
| 138 | + /// codebook layout. Drift here means drift from the OGAR codebook. |
| 139 | + #[test] |
| 140 | + fn project_arm_ids_are_dense_monotone_0x0101_through_0x011a() { |
| 141 | + let ids = [ |
| 142 | + PROJECT, |
| 143 | + PROJECT_WORK_ITEM, |
| 144 | + BILLABLE_WORK_ENTRY, |
| 145 | + PROJECT_ACTOR, |
| 146 | + PROJECT_STATUS, |
| 147 | + PROJECT_TYPE, |
| 148 | + PRIORITY, |
| 149 | + PROJECT_MEMBERSHIP, |
| 150 | + PROJECT_JOURNAL, |
| 151 | + PROJECT_REPOSITORY, |
| 152 | + PROJECT_VERSION, |
| 153 | + PROJECT_WIKI_PAGE, |
| 154 | + PROJECT_QUERY, |
| 155 | + PROJECT_ATTACHMENT, |
| 156 | + PROJECT_COMMENT, |
| 157 | + PROJECT_CUSTOM_FIELD, |
| 158 | + PROJECT_RELATION, |
| 159 | + PROJECT_CHANGESET, |
| 160 | + PROJECT_WATCHER, |
| 161 | + PROJECT_NEWS, |
| 162 | + PROJECT_MESSAGE, |
| 163 | + PROJECT_FORUM, |
| 164 | + PROJECT_ROLE, |
| 165 | + PROJECT_MEMBER_ROLE, |
| 166 | + PROJECT_CUSTOM_VALUE, |
| 167 | + PROJECT_ENABLED_MODULE, |
| 168 | + ]; |
| 169 | + assert_eq!(ids.len(), 26); |
| 170 | + // All in the project-management arm. |
| 171 | + for id in ids { |
| 172 | + assert!( |
| 173 | + (0x0101..=0x011A).contains(&id), |
| 174 | + "id 0x{id:04X} outside project-arm block 0x0101..=0x011A" |
| 175 | + ); |
| 176 | + } |
| 177 | + // Strictly monotone (codebook order pin). |
| 178 | + for w in ids.windows(2) { |
| 179 | + assert!(w[0] < w[1], "0x{:04X} should precede 0x{:04X}", w[0], w[1]); |
| 180 | + } |
| 181 | + } |
| 182 | +} |
0 commit comments