1515//!
1616//! # The two halves of a classid (`APP-CLASS-CODEBOOK-LAYOUT.md`)
1717//!
18+ //! Since the 2026-07-02 canon:custom half-order flip (lance-graph
19+ //! `classid-canon-custom-flip-v1` P1, OGAR flipped in lockstep) the CANON
20+ //! concept sits HIGH and the APP/render prefix LOW:
21+ //!
1822//! ```text
19- //! classid : u32 = [ hi u16 : APP / render prefix ] [ lo u16 : concept ]
20- //! 0x0001 (OpenProject ) 0xDDCC (shared )
23+ //! classid : u32 = [ hi u16 : concept ] [ lo u16 : APP / render prefix ]
24+ //! 0xDDCC (shared ) 0x0001 (OpenProject )
2125//! ```
2226//!
23- //! - **low u16 — the shared canonical concept.** What the object *is*: the
27+ //! - **high u16 — the shared canonical concept.** What the object *is*: the
2428//! RBAC + ontology + cross-app identity key. This is what the port pull
2529//! returns, and it is identical to the id Redmine's `Issue` pulls
2630//! (`project_work_item = 0x0102`). The shared currency.
27- //! - **high u16 — OpenProject's render prefix [`APP_PREFIX`] (`0x0001`).**
31+ //! - **low u16 — OpenProject's render prefix [`APP_PREFIX`] (`0x0001`).**
2832//! *Whose* rendering: OpenProject's `ClassView` / template lens. Reserved
2933//! for OpenProject in `APP-CLASS-CODEBOOK-LAYOUT.md` §2; Redmine's twin is
30- //! `0x0007`. A full render classid is `0x0001_DDCC `.
34+ //! `0x0007`. A full render classid is `0xDDCC_0001 `.
3135//!
3236//! This module composes only OpenProject's `0x0001` render of the W0
3337//! "two renders, one concept" pair; the cross-fork convergence itself (that
34- //! OpenProject `WorkPackage` and Redmine `Issue` share the low half `0x0102`)
38+ //! OpenProject `WorkPackage` and Redmine `Issue` share the concept `0x0102`)
3539//! is machine-checked **upstream** in OGAR's port tests
3640//! (`openproject_and_redmine_converge_on_shared_concepts`), not here.
3741//!
4347//! [`ogar_vocab::ports::OpenProjectPort::APP_PREFIX`] — the typed §2
4448//! allocation-table value, not a local literal.
4549//! - [`render_classid`] re-exports `ogar_vocab::app::render_classid_for::<OpenProjectPort>`
46- //! — the central `(prefix << 16) | concept ` composition; one place owns the
47- //! bit math.
50+ //! — the central `((concept as u32) << 16) | prefix ` composition; one place
51+ //! owns the bit math.
4852//! - [`app_of`] / [`concept_of`] re-export `ogar_vocab::app::{app_of, concept_of}`
49- //! — the inverse decomposition (`0x0001_DDCC ` → prefix + concept), so a render
53+ //! — the inverse decomposition (`0xDDCC_0001 ` → prefix + concept), so a render
5054//! classid round-trips entirely on this crate's surface without reaching into
5155//! `ogar_vocab::app`.
5256//! - [`canonical_concept_name`] re-exports `ogar_vocab::canonical_concept_name`
5963
6064use ogar_vocab:: ports:: { OpenProjectPort , PortSpec } ;
6165
62- /// OpenProject's reserved **APP / render prefix** — the high u16 of a full
63- /// `classid` (`APP-CLASS-CODEBOOK-LAYOUT.md` §2 allocation table). Pairs with
64- /// Redmine's `0x0007`: same low-half concept, different render lens.
66+ /// OpenProject's reserved **APP / render prefix** — the CUSTOM (low, since
67+ /// the 2026-07-02 flip) u16 of a full `classid`
68+ /// (`APP-CLASS-CODEBOOK-LAYOUT.md` §2 allocation table; the VALUE is
69+ /// order-invariant). Pairs with Redmine's `0x0007`: same canon concept,
70+ /// different render lens.
6571///
6672/// `pub const` re-export of [`ogar_vocab::ports::OpenProjectPort::APP_PREFIX`]
6773/// (OGAR PR #97). Promoted from a local mirror to the typed upstream constant —
@@ -88,7 +94,7 @@ pub fn class_id_of(surface_name: &str) -> Option<u16> {
8894}
8995
9096/// Compose the full 32-bit **render** classid for a shared `concept` under
91- /// OpenProject's prefix: `0x0001_DDCC` .
97+ /// OpenProject's prefix: `0xDDCC_0001` (concept HIGH, prefix LOW) .
9298///
9399/// Re-export of `ogar_vocab::app::render_classid_for::<OpenProjectPort>(concept)`
94100/// (OGAR PR #97) — the central composition, not local bit math. Pure stamp: it
@@ -98,54 +104,55 @@ pub fn class_id_of(surface_name: &str) -> Option<u16> {
98104///
99105/// ```
100106/// use op_canon::{app, class_ids};
101- /// assert_eq!(app::render_classid(class_ids::PROJECT_WORK_ITEM), 0x0001_0102 );
107+ /// assert_eq!(app::render_classid(class_ids::PROJECT_WORK_ITEM), 0x0102_0001 );
102108/// ```
103109#[ must_use]
104110pub fn render_classid ( concept : u16 ) -> u32 {
105111 ogar_vocab:: app:: render_classid_for :: < OpenProjectPort > ( concept)
106112}
107113
108114/// Pull + compose in one step: an OpenProject surface name → its full render
109- /// classid `0x0001_DDCC `, via the OGAR port. `None` if the port does not carry
115+ /// classid `0xDDCC_0001 `, via the OGAR port. `None` if the port does not carry
110116/// the name.
111117///
112118/// ```
113119/// use op_canon::app;
114- /// assert_eq!(app::render_classid_of("WorkPackage"), Some(0x0001_0102 ));
120+ /// assert_eq!(app::render_classid_of("WorkPackage"), Some(0x0102_0001 ));
115121/// ```
116122#[ must_use]
117123pub fn render_classid_of ( surface_name : & str ) -> Option < u32 > {
118124 class_id_of ( surface_name) . map ( render_classid)
119125}
120126
121127/// Decompose a 32-bit render classid into its **OpenProject render prefix**
122- /// (the high u16) — the inverse of [`render_classid`]'s high half.
128+ /// (the low u16 since the flip) — the inverse of [`render_classid`]'s
129+ /// prefix half.
123130///
124131/// Re-export of `ogar_vocab::app::app_of` (OGAR PR #97) — the central
125132/// decomposition, not local bit math; paired with [`concept_of`]. For any
126133/// concept, `app_of(render_classid(concept)) == APP_PREFIX`.
127134///
128135/// ```
129136/// use op_canon::{app, class_ids};
130- /// let cid = app::render_classid(class_ids::PROJECT_WORK_ITEM); // 0x0001_0102
137+ /// let cid = app::render_classid(class_ids::PROJECT_WORK_ITEM); // 0x0102_0001
131138/// assert_eq!(app::app_of(cid), app::APP_PREFIX); // 0x0001
132139/// ```
133140#[ must_use]
134141pub fn app_of ( classid : u32 ) -> u16 {
135142 ogar_vocab:: app:: app_of ( classid)
136143}
137144
138- /// Decompose a 32-bit render classid into its **shared concept** (the low
139- /// u16) — the inverse of [`render_classid`]'s low half, recovering the
140- /// cross-app currency a [`class_id_of`] pull returns.
145+ /// Decompose a 32-bit render classid into its **shared concept** (the CANON
146+ /// high u16 since the flip ) — the inverse of [`render_classid`]'s concept
147+ /// half, recovering the cross-app currency a [`class_id_of`] pull returns.
141148///
142149/// Re-export of `ogar_vocab::app::concept_of` (OGAR PR #97), paired with
143150/// [`app_of`]. For any concept, `concept_of(render_classid(concept)) == concept`;
144151/// this is the id Redmine's twin carries under prefix `0x0007`.
145152///
146153/// ```
147154/// use op_canon::{app, class_ids};
148- /// let cid = app::render_classid(class_ids::PROJECT_WORK_ITEM); // 0x0001_0102
155+ /// let cid = app::render_classid(class_ids::PROJECT_WORK_ITEM); // 0x0102_0001
149156/// assert_eq!(app::concept_of(cid), class_ids::PROJECT_WORK_ITEM); // 0x0102
150157/// ```
151158#[ must_use]
@@ -154,7 +161,7 @@ pub fn concept_of(classid: u32) -> u16 {
154161}
155162
156163/// Reverse of [`class_id_of`] at the concept level: a shared canonical
157- /// **concept id** (the low u16 / port-pull currency) → its canonical
164+ /// **concept id** (the canon-half / port-pull currency) → its canonical
158165/// concept **name** (e.g. `project_work_item`). `None` for an id the
159166/// codebook does not carry.
160167///
@@ -217,13 +224,13 @@ mod tests {
217224
218225 #[ test]
219226 fn render_classid_composes_openproject_prefix ( ) {
220- // Full render classid = 0x0001_DDCC (W0 worked table ).
221- assert_eq ! ( render_classid( class_ids:: PROJECT_WORK_ITEM ) , 0x0001_0102 ) ;
222- assert_eq ! ( render_classid( class_ids:: BILLABLE_WORK_ENTRY ) , 0x0001_0103 ) ;
223- assert_eq ! ( render_classid( class_ids:: PROJECT_ROLE ) , 0x0001_0117 ) ;
227+ // Full render classid = 0xDDCC_0001 (concept HIGH since the flip ).
228+ assert_eq ! ( render_classid( class_ids:: PROJECT_WORK_ITEM ) , 0x0102_0001 ) ;
229+ assert_eq ! ( render_classid( class_ids:: BILLABLE_WORK_ENTRY ) , 0x0103_0001 ) ;
230+ assert_eq ! ( render_classid( class_ids:: PROJECT_ROLE ) , 0x0117_0001 ) ;
224231 // Pull + compose.
225- assert_eq ! ( render_classid_of( "WorkPackage" ) , Some ( 0x0001_0102 ) ) ;
226- assert_eq ! ( render_classid_of( "TimeEntry" ) , Some ( 0x0001_0103 ) ) ;
232+ assert_eq ! ( render_classid_of( "WorkPackage" ) , Some ( 0x0102_0001 ) ) ;
233+ assert_eq ! ( render_classid_of( "TimeEntry" ) , Some ( 0x0103_0001 ) ) ;
227234 }
228235
229236 #[ test]
@@ -245,10 +252,10 @@ mod tests {
245252 }
246253
247254 #[ test]
248- fn render_classid_keeps_concept_in_the_low_half ( ) {
249- // The low half is the shared concept (== the port pull); the high
250- // half is OpenProject's render lens. Redmine's twin carries the SAME
251- // low half under prefix 0x0007 (pinned in OGAR's port tests).
255+ fn render_classid_keeps_concept_in_the_canon_half ( ) {
256+ // The CANON (high) half is the shared concept (== the port pull); the
257+ // low half is OpenProject's render lens. Redmine's twin carries the
258+ // SAME canon half under prefix 0x0007 (pinned in OGAR's port tests).
252259 for & concept in & [
253260 class_ids:: PROJECT_WORK_ITEM ,
254261 class_ids:: PROJECT ,
@@ -261,12 +268,12 @@ mod tests {
261268 assert_eq ! (
262269 ogar_vocab:: app:: app_of( cid) ,
263270 APP_PREFIX ,
264- "high half = OpenProject lens" ,
271+ "custom half = OpenProject lens" ,
265272 ) ;
266273 assert_eq ! (
267274 ogar_vocab:: app:: concept_of( cid) ,
268275 concept,
269- "low half = shared concept" ,
276+ "canon half = shared concept" ,
270277 ) ;
271278 }
272279 }
@@ -295,8 +302,9 @@ mod tests {
295302 let cid = render_classid ( class_ids:: PROJECT_WORK_ITEM ) ;
296303 assert_eq ! ( app_of( cid) , ogar_vocab:: app:: app_of( cid) ) ;
297304 assert_eq ! ( concept_of( cid) , ogar_vocab:: app:: concept_of( cid) ) ;
298- // And the full decomposition reconstructs the classid bit-for-bit.
299- assert_eq ! ( ( ( app_of( cid) as u32 ) << 16 ) | ( concept_of( cid) as u32 ) , cid, ) ;
305+ // And the full decomposition reconstructs the classid bit-for-bit
306+ // (concept HIGH, prefix LOW since the flip).
307+ assert_eq ! ( ( ( concept_of( cid) as u32 ) << 16 ) | ( app_of( cid) as u32 ) , cid, ) ;
300308 }
301309
302310 #[ test]
0 commit comments