Skip to content

Commit 556da18

Browse files
authored
Merge pull request #68 from AdaWorldAPI/claude/v3-substrate-migration-review-o0yoxv
op-canon: classid canon:custom half-order flip — literals + hi/lo docs to canon-HIGH
2 parents 4a852b3 + d9f1de5 commit 556da18

2 files changed

Lines changed: 63 additions & 37 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# 2026-07-02 — classid canon:custom half-order flip (canon HIGH / prefix LOW)
2+
3+
The 2026-06-30 convergence assessment's classid examples use the PRE-FLIP
4+
order and are historical. As of 2026-07-02 (operator trigger, lance-graph
5+
`classid-canon-custom-flip-v1` P1 + OGAR lockstep flip):
6+
7+
- `classid : u32 = [hi u16: CANON concept][lo u16: APP/render prefix]`
8+
- `openproject:WorkPackage``0x0102_0001` (was `0x0001_0102`)
9+
- `redmine:Issue``0x0102_0007` (was `0x0007_0102`)
10+
- `APP_PREFIX` VALUES unchanged (OP `0x0001`, Redmine `0x0007`) — only the
11+
position moved to the LOW half.
12+
- `op_canon::app` re-exports flipped in lockstep with `ogar_vocab::app`
13+
(this repo owns no bit math); literal-pinned tests/doctests updated.
14+
- Pre-flip persisted ids resolve upstream via read-only legacy registry
15+
aliases (mint-forward; retirement gated on corpus proof).
16+
17+
This branch's op-canon changes are gated on the OGAR flip PR merging to
18+
OGAR main (the git dep tracks `branch = "main"`).

crates/op-canon/src/app.rs

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,27 @@
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
//!
@@ -43,10 +47,10 @@
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`
@@ -59,9 +63,11 @@
5963
6064
use 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]
104110
pub 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]
117123
pub 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]
134141
pub 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

Comments
 (0)