1- //! **APP‖ class composition** — the high-u16 render-prefix machinery
1+ //! **class‖APP composition** — the render-prefix machinery
22//! (`docs/APP-CLASS-CODEBOOK-LAYOUT.md` §0, §4).
33//!
4- //! A full 32-bit `classid` is two orthogonal halves:
4+ //! A full 32-bit `classid` is two orthogonal halves. Since the 2026-07-02
5+ //! canon:custom half-order flip (lance-graph
6+ //! `classid-canon-custom-flip-v1.md` P1/P2 — the operator's `0x07:01::1000`
7+ //! ruling), the CANON concept sits HIGH and the APP/render prefix LOW:
58//!
69//! ```text
7- //! classid : u32 = [ hi u16 : APP / render prefix ] [ lo u16 : concept ]
8- //! 0xAAAA (per-app ClassView lens) 0xDDCC (shared RBAC+ontology )
10+ //! classid : u32 = [ hi u16 : CANON concept ] [ lo u16 : APP / render prefix ]
11+ //! 0xDDCC (shared RBAC+ontology) 0xAAAA (per-app ClassView lens)
912//! ```
1013//!
1114//! The per-port reserved prefix is [`PortSpec::APP_PREFIX`] (the §2 allocation
12- //! table as typed data); this module composes and decomposes the full id so
13- //! consumers re-export ONE source of the bit math instead of each
14- //! re-implementing `(prefix << 16) | concept`. `0x0000` is the shared
15- //! canonical core (every existing id is `0x0000_DDCC` — additive, invariant
16- //! I-APP1).
15+ //! table as typed data; the prefix VALUES are order-invariant); this module
16+ //! composes and decomposes the full id so consumers re-export ONE source of
17+ //! the bit math instead of each re-implementing
18+ //! `((concept as u32) << 16) | prefix`. `0x0000` is the shared canonical core
19+ //! (every existing id renders under the core lens as `0xDDCC_0000` —
20+ //! additive, invariant I-APP1). Pre-flip stored ids (`0x0000_DDCC` /
21+ //! `0xAAAA_DDCC`) are the LEGACY order — readers of persisted data resolve
22+ //! them through concrete legacy-alias registry keys, never by
23+ //! reinterpreting the halves (mint-forward).
1724//!
18- //! Composing the high half is **reserving/stamping**, not minting a class_id
19- //! (§2: "reserving costs nothing") — concept (low-u16) mints stay gated on the
20- //! 5+3 codebook pass. The low half is the cross-app currency: concept/domain
21- //! routing reads it alone (`lance_graph_contract::classid_concept_domain`
22- //! does `… as u16`), so it is identical under every render prefix.
25+ //! Composing the prefix half is **reserving/stamping**, not minting a class_id
26+ //! (§2: "reserving costs nothing") — concept mints stay gated on the
27+ //! 5+3 codebook pass. The concept half is the cross-app currency:
28+ //! concept/domain routing reads it alone (`lance_graph_contract::
29+ //! classid_concept_domain` routes the canon half), so it is identical under
30+ //! every render prefix.
2331
2432use crate :: ports:: PortSpec ;
2533
26- /// Compose a full render `classid` from an app `prefix` (high u16) and a
27- /// canonical `concept` id (low u16): `(prefix << 16) | concept`.
34+ /// Compose a full render `classid` from an app `prefix` (CUSTOM half, low
35+ /// u16) and a canonical `concept` id (CANON half, high u16):
36+ /// `((concept as u32) << 16) | prefix`.
2837///
29- /// `render_classid(0x0001, 0x0102)` → `0x0001_0102 ` (OpenProject's
38+ /// `render_classid(0x0001, 0x0102)` → `0x0102_0001 ` (OpenProject's
3039/// `project_work_item`); the Redmine twin `render_classid(0x0007, 0x0102)` →
31- /// `0x0007_0102 ` — same concept, different render lens.
40+ /// `0x0102_0007 ` — same concept, different render lens.
3241#[ must_use]
3342pub const fn render_classid ( prefix : u16 , concept : u16 ) -> u32 {
34- ( ( prefix as u32 ) << 16 ) | ( concept as u32 )
43+ ( ( concept as u32 ) << 16 ) | ( prefix as u32 )
3544}
3645
3746/// Compose a render `classid` for a specific port, reading its reserved
3847/// [`PortSpec::APP_PREFIX`]: `render_classid_for::<OpenProjectPort>(concept)`
39- /// → `0x0001_DDCC `. This is the helper consumers call so the prefix and the
48+ /// → `0xDDCC_0001 `. This is the helper consumers call so the prefix and the
4049/// bit math both come from OGAR (one source of truth), not a local literal.
4150#[ must_use]
4251pub fn render_classid_for < P : PortSpec > ( concept : u16 ) -> u32 {
4352 render_classid ( P :: APP_PREFIX , concept)
4453}
4554
46- /// The APP / render prefix — the **high u16** of a full `classid`. `0x0000`
47- /// ([the shared core]) is the abstract/default-`ClassView` anchor; a non-zero
48- /// value selects an app's render lens. This is the §4 `resolve_codebook`
49- /// routing key (`classid >> 16`).
55+ /// The APP / render prefix — the CUSTOM half (**low u16** since the flip) of
56+ /// a full `classid`. `0x0000` ([the shared core]) is the
57+ /// abstract/default-`ClassView` anchor; a non-zero value selects an app's
58+ /// render lens. This is the §4 `resolve_codebook` routing key
59+ /// (`classid as u16`).
5060///
5161/// [the shared core]: PortSpec::APP_PREFIX
5262#[ must_use]
5363pub const fn app_of ( classid : u32 ) -> u16 {
54- ( classid >> 16 ) as u16
64+ classid as u16
5565}
5666
57- /// The canonical concept id — the **low u16** of a full `classid`. The shared
58- /// RBAC + ontology + cross-app identity key; concept/domain routing reads only
59- /// this half, so it is identical for every render prefix.
67+ /// The canonical concept id — the CANON half (**high u16** since the flip) of
68+ /// a full `classid`. The shared RBAC + ontology + cross-app identity key;
69+ /// concept/domain routing reads only this half, so it is identical for every
70+ /// render prefix.
6071#[ must_use]
6172pub const fn concept_of ( classid : u32 ) -> u16 {
62- classid as u16
73+ ( classid >> 16 ) as u16
6374}
6475
6576#[ cfg( test) ]
@@ -70,9 +81,10 @@ mod tests {
7081
7182 #[ test]
7283 fn render_classid_composes_the_two_halves ( ) {
73- assert_eq ! ( render_classid( 0x0001 , 0x0102 ) , 0x0001_0102 ) ;
74- assert_eq ! ( render_classid( 0x0007 , 0x0102 ) , 0x0007_0102 ) ;
75- assert_eq ! ( render_classid( 0x0002 , 0x0202 ) , 0x0002_0202 ) ;
84+ // Canon-high order (2026-07-02 flip): concept HIGH, prefix LOW.
85+ assert_eq ! ( render_classid( 0x0001 , 0x0102 ) , 0x0102_0001 ) ;
86+ assert_eq ! ( render_classid( 0x0007 , 0x0102 ) , 0x0102_0007 ) ;
87+ assert_eq ! ( render_classid( 0x0002 , 0x0202 ) , 0x0202_0002 ) ;
7688 }
7789
7890 #[ test]
@@ -81,22 +93,22 @@ mod tests {
8193 // under different prefixes — "two renders, one concept" (§1).
8294 assert_eq ! (
8395 render_classid_for:: <OpenProjectPort >( class_ids:: PROJECT_WORK_ITEM ) ,
84- 0x0001_0102 ,
96+ 0x0102_0001 ,
8597 ) ;
8698 assert_eq ! (
8799 render_classid_for:: <RedminePort >( class_ids:: PROJECT_WORK_ITEM ) ,
88- 0x0007_0102 ,
100+ 0x0102_0007 ,
89101 ) ;
90102 assert_eq ! (
91103 render_classid_for:: <OdooPort >( class_ids:: COMMERCIAL_DOCUMENT ) ,
92- 0x0002_0202 ,
104+ 0x0202_0002 ,
93105 ) ;
94106 }
95107
96108 #[ test]
97109 fn app_of_and_concept_of_decompose ( ) {
98110 let cid = render_classid ( 0x0005 , class_ids:: PATIENT ) ; // Medcare patient
99- assert_eq ! ( cid, 0x0005_0901 ) ;
111+ assert_eq ! ( cid, 0x0901_0005 ) ;
100112 assert_eq ! ( app_of( cid) , 0x0005 ) ;
101113 assert_eq ! ( concept_of( cid) , class_ids:: PATIENT ) ;
102114 }
@@ -119,18 +131,19 @@ mod tests {
119131
120132 #[ test]
121133 fn core_prefix_is_additive_and_bit_identical ( ) {
122- // I-APP1/I-APP5: a core (hi=0x0000) classid equals the bare concept
123- // widened to u32 — no renumber, no version cost.
134+ // I-APP1/I-APP5 (post-flip form): a core (prefix=0x0000) classid is
135+ // the bare concept in the CANON (high) half — no renumber, no version
136+ // cost; `concept_of` recovers it exactly.
124137 let core = render_classid ( 0x0000 , class_ids:: PROJECT_WORK_ITEM ) ;
125- assert_eq ! ( core, 0x0000_0102 ) ;
126- assert_eq ! ( core, u32 :: from( class_ids:: PROJECT_WORK_ITEM ) ) ;
138+ assert_eq ! ( core, 0x0102_0000 ) ;
139+ assert_eq ! ( core, u32 :: from( class_ids:: PROJECT_WORK_ITEM ) << 16 ) ;
127140 assert_eq ! ( app_of( core) , 0x0000 ) ;
128141 assert_eq ! ( concept_of( core) , class_ids:: PROJECT_WORK_ITEM ) ;
129142 }
130143
131144 #[ test]
132145 fn render_prefix_never_changes_the_concept_half ( ) {
133- // The high half is the render lens; it must not perturb the low-half
146+ // The prefix half is the render lens; it must not perturb the CANON
134147 // concept that RBAC + ontology key on.
135148 let op = render_classid_for :: < OpenProjectPort > ( class_ids:: BILLABLE_WORK_ENTRY ) ;
136149 let rm = render_classid_for :: < RedminePort > ( class_ids:: BILLABLE_WORK_ENTRY ) ;
0 commit comments