From b9fce524ea60283d25d6b4674467cc5ec1a9593d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 19:35:43 +0000 Subject: [PATCH 1/5] vocab: mint Mail domain (0x05XX: mailbox, mail_message) + Spear port 0x000A The mail hub (spear) and the stalwart dir-sim directory need a classid to declare an RBAC plug against; nothing mail-shaped was minted. 0x05XX is the next clean domain slot (0x10XX would put a domain root on the V3 marker 0x1000). Paired with the lance-graph ogar_codebook mirror. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-vocab/src/capability_registry.rs | 2 +- crates/ogar-vocab/src/lib.rs | 76 ++++++++++++++++++-- crates/ogar-vocab/src/ports.rs | 31 ++++++++ docs/APP-CLASS-CODEBOOK-LAYOUT.md | 3 +- 4 files changed, 105 insertions(+), 7 deletions(-) diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index c477b5c8..f9b39c0b 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -377,7 +377,7 @@ fn resolve_concept_row(id: u16) -> Option<(&'static str, u16)> { mod the_canon_carries_no_palette_rows { #[test] fn no_0x17xx_row_reached_the_globally_mirrored_codebook() { - assert_eq!(crate::class_ids::ALL.len(), 98); + assert_eq!(crate::class_ids::ALL.len(), 100); for (_, id) in crate::class_ids::ALL { assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook"); } diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index 353d8196..aa19ca90 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -1106,7 +1106,7 @@ impl Class { /// 0x02XX commerce / ERP (OSB ↔ Odoo cross-curator) /// 0x03XX Ontology (OBO biomedical reference: MONDO/HPO/Uberon/PATO/RO — zero rows here; concepts in ogar-obo) /// 0x04XX Weather / Atmosphere (forecast + atmospheric reference cells) -/// 0x05XX unassigned +/// 0x05XX Mail (mailbox + message; consumed by spear / stalwart) /// 0x06XX unassigned /// 0x07XX reserved: OSINT /// 0x08XX OCR (container kinds: unicharset/recoder/charset) @@ -1240,6 +1240,12 @@ const CODEBOOK: &[(&str, u16)] = &[ // ClassView selection remains in the low u16 of the full classid. ("weather_cell", 0x0401), ("weather_static_cell", 0x0402), + // ── 0x05XX — Mail domain ── + // The two things a mail store authorizes: the mailbox (whose access follows + // its owner in the directory) and the message inside it. Folders, flags and + // threads are rows and columns of these, not concepts of their own. + ("mailbox", 0x0501), + ("mail_message", 0x0502), // ── 0x07XX — OSINT domain: ZERO vocabulary rows BY DESIGN (operator // ruling 2026-07-02, corrects PR #145's two hallucinated concept mints // `osint_system@0x0700` / `osint_person@0x0701`). Within the OSINT domain @@ -1479,6 +1485,10 @@ pub enum ConceptDomain { /// `0x04XX` — Weather / Atmosphere. Shared atmospheric and forecast-grid /// concepts; public environmental reference data, not an OSM extension. Weather, + /// `0x05XX` — Mail. The mailbox and the message, as one mail store + /// authorizes them; consumed by spear (the mail hub) and the stalwart + /// dir-sim directory. + Mail, /// `0x07XX` — OSINT (open-source intelligence). Osint, /// `0x08XX` — OCR (optical character recognition / document @@ -1754,7 +1764,7 @@ pub enum ConceptDomain { /// reads the compartment off the key (`classid >> 24`) instead of looking /// it up. Form, - /// Any high-byte slot not yet assigned a domain (`0x05XX`–`0x06XX`, + /// Any high-byte slot not yet assigned a domain (`0x06XX`, /// `0x10XX`–`0x16XX`, `0x18XX`–`0xBFXX`, `0xC2XX`–`0xC3XX`, /// `0xC5XX`, `0xC7XX`+). Unassigned, @@ -1770,6 +1780,7 @@ pub fn canonical_concept_domain(id: u16) -> ConceptDomain { 0x02 => ConceptDomain::Commerce, 0x03 => ConceptDomain::Ontology, 0x04 => ConceptDomain::Weather, + 0x05 => ConceptDomain::Mail, 0x07 => ConceptDomain::Osint, 0x08 => ConceptDomain::Ocr, 0x09 => ConceptDomain::Health, @@ -2051,6 +2062,14 @@ pub mod class_ids { /// geography-derived weather context, distinct from dynamic fields. pub const WEATHER_STATIC_CELL: u16 = 0x0402; + // ── 0x05XX — Mail domain ── + + /// `mailbox` (`0x0501`) — one mailbox. Access follows the directory object + /// that owns it. + pub const MAILBOX: u16 = 0x0501; + /// `mail_message` (`0x0502`) — one message in a mailbox. + pub const MAIL_MESSAGE: u16 = 0x0502; + // ── 0x08XX — OCR domain (document extraction; the Tesseract-rs arc) ── // Class-level container KINDS only: the concept slots name the container // types the Core resolves — never their content. The 112 unichars of a @@ -2369,6 +2388,9 @@ pub mod class_ids { // 0x04XX — Weather / Atmosphere ("weather_cell", WEATHER_CELL), ("weather_static_cell", WEATHER_STATIC_CELL), + // 0x05XX — Mail + ("mailbox", MAILBOX), + ("mail_message", MAIL_MESSAGE), // 0x07XX — OSINT: ZERO vocabulary rows BY DESIGN (operator ruling // 2026-07-02; see the CODEBOOK 0x07XX section note). No entries // follow — OGAR vocabulary carries no OSINT concept names. @@ -2504,7 +2526,7 @@ pub mod class_ids { // Pin the number here so a bump is never silent. assert_eq!( ALL.len(), - 98, + 100, "class_ids::ALL count changed — update this pin AND land the \ corresponding row in lance-graph's \ crates/lance-graph-contract/src/ogar_codebook.rs::CODEBOOK \ @@ -3331,6 +3353,8 @@ pub fn all_promoted_classes() -> Vec { // 0x08XX — OCR arm (9 container kinds), in class_ids::ALL order. weather_cell(), weather_static_cell(), + mailbox(), + mail_message(), unicharset(), recoder(), charset(), @@ -5034,6 +5058,29 @@ pub fn weather_static_cell() -> Class { c } +// ───────────────────────────────────────────────────────────────────── +// 0x05XX — Mail canonical builders. Identity only; the message columns live +// in the consumer's ClassView (spear's `messages` Lance schema). +// ───────────────────────────────────────────────────────────────────── + +/// `mailbox` (`0x0501`) — one mailbox. +#[must_use] +pub fn mailbox() -> Class { + let mut c = Class::new("Mailbox"); + c.language = Language::Unknown; + c.canonical_concept = Some("mailbox".to_string()); + c +} + +/// `mail_message` (`0x0502`) — one message in a mailbox. +#[must_use] +pub fn mail_message() -> Class { + let mut c = Class::new("MailMessage"); + c.language = Language::Unknown; + c.canonical_concept = Some("mail_message".to_string()); + c +} + // ───────────────────────────────────────────────────────────────────── // 0xC6XX — MMIO hardware-platform builders (C64 = founding tenant). // Kind-level containers only: registers/opcodes are content rows. @@ -6050,10 +6097,11 @@ mod tests { // lives in ogar-obo; plug-and-play, never pulls into ERP consumers). assert_eq!(canonical_concept_domain(0x0300), ConceptDomain::Ontology); assert_eq!(canonical_concept_domain(0x03AB), ConceptDomain::Ontology); - // Weather / Atmosphere block (0x04), then still-unassigned 0x05-0x06. + // Weather / Atmosphere block (0x04), Mail (0x05), then still-unassigned 0x06. assert_eq!(canonical_concept_domain(0x0400), ConceptDomain::Weather); assert_eq!(canonical_concept_domain(0x0401), ConceptDomain::Weather); - assert_eq!(canonical_concept_domain(0x0500), ConceptDomain::Unassigned); + assert_eq!(canonical_concept_domain(0x0500), ConceptDomain::Mail); + assert_eq!(canonical_concept_domain(0x05FF), ConceptDomain::Mail); assert_eq!(canonical_concept_domain(0x0600), ConceptDomain::Unassigned); // HR block (0x0D). assert_eq!(canonical_concept_domain(0x0D00), ConceptDomain::HR); @@ -7227,3 +7275,21 @@ mod weather_classid_mint_tests { ); } } + +#[cfg(test)] +mod mail_classid_mint_tests { + use super::*; + + #[test] + fn mail_domain_and_codebook_resolve() { + for id in [class_ids::MAILBOX, class_ids::MAIL_MESSAGE] { + assert_eq!(canonical_concept_domain(id), ConceptDomain::Mail); + } + assert_eq!(canonical_concept_id("mailbox"), Some(0x0501)); + assert_eq!(canonical_concept_id("mail_message"), Some(0x0502)); + assert_eq!(mailbox().canonical_id(), Some(class_ids::MAILBOX)); + assert_eq!(mail_message().canonical_id(), Some(class_ids::MAIL_MESSAGE)); + // 0x06 stays unassigned. + assert_eq!(canonical_concept_domain(0x0600), ConceptDomain::Unassigned); + } +} diff --git a/crates/ogar-vocab/src/ports.rs b/crates/ogar-vocab/src/ports.rs index 6947a721..7b68b9f3 100644 --- a/crates/ogar-vocab/src/ports.rs +++ b/crates/ogar-vocab/src/ports.rs @@ -639,11 +639,42 @@ pub const WEATHERNEXT_ALIASES: &[(&str, u16)] = &[ ("WeatherStaticCell", class_ids::WEATHER_STATIC_CELL), ]; +// ── Spear (the mail hub) port ──────────────────────────────────────── + +/// Spear's `PortSpec` over the shared Mail (`0x05XX`) concepts. `0x000A` is +/// Spear's render skin; the mailbox and message meanings stay in the high u16. +pub struct SpearPort; + +impl PortSpec for SpearPort { + const NAMESPACE: &'static str = "Spear"; + const BRIDGE_ID: &'static str = "spear"; + const APP_PREFIX: u16 = 0x000A; + fn aliases() -> &'static [(&'static str, u16)] { + SPEAR_ALIASES + } +} + +/// Spear's names for the shared mail concepts. +pub const SPEAR_ALIASES: &[(&str, u16)] = &[ + ("Mailbox", class_ids::MAILBOX), + ("Message", class_ids::MAIL_MESSAGE), +]; + #[cfg(test)] mod tests { use super::*; use crate::app::{app_of, concept_of, render_classid}; + #[test] + fn spear_classview_composes_canon_high_custom_low() { + assert_eq!(SpearPort::class_id("Mailbox"), Some(0x0501)); + assert_eq!(SpearPort::class_id("Message"), Some(0x0502)); + let mailbox = render_classid(SpearPort::APP_PREFIX, class_ids::MAILBOX); + assert_eq!(mailbox, 0x0501_000A); + assert_eq!(concept_of(mailbox), 0x0501); + assert_eq!(app_of(mailbox), 0x000A); + } + #[test] fn weathernext_classview_composes_canon_high_custom_low() { assert_eq!(WeatherNextPort::APP_PREFIX, 0x0009); diff --git a/docs/APP-CLASS-CODEBOOK-LAYOUT.md b/docs/APP-CLASS-CODEBOOK-LAYOUT.md index 29887537..da98ce3d 100644 --- a/docs/APP-CLASS-CODEBOOK-LAYOUT.md +++ b/docs/APP-CLASS-CODEBOOK-LAYOUT.md @@ -171,7 +171,7 @@ materialised until the app mints its first private class). | `lo u16` | App / namespace | Core domain(s) it consumes | Private codebook today? | |---|---|---|---| -| `0x0000` | **Shared canonical core** | all (`0x01/02/04/07/08/09` + `0x0A` anatomy + `0x0B` auth + `0x0C` automation) | n/a (this *is* core) | +| `0x0000` | **Shared canonical core** | all (`0x01/02/04/05/07/08/09` + `0x0A` anatomy + `0x0B` auth + `0x0C` automation) | n/a (this *is* core) | | `0x0001` | OpenProject (openproject-nexgen-rs) | `0x01` project-mgmt | **no** — maps onto core | | `0x0002` | Odoo | `0x02` commerce | **no** — maps onto core (converge `od-ontology`) | | `0x0003` | WoA / woa-rs | `0x02` commerce (work orders) | **no** — maps onto core | @@ -181,6 +181,7 @@ materialised until the app mints its first private class). | `0x0007` | Redmine | `0x01` project-mgmt | **no** — same concepts as OpenProject, own templates | | `0x0008` | OpenStreetMap (openstreetmap-website-rs) | `0x0F` geo | **no** — maps entirely onto core | | `0x0009` | WeatherNext / weathernext-rs | `0x04` weather / atmosphere | **no** — maps onto core | +| `0x000A` | Spear (mail hub) | `0x05` mail | **no** — maps onto core | | `0x00A0` | (reserved) future app block | — | — | > **OpenProject (`0x0001`) and Redmine (`0x0007`) are the showcase:** From 9dce4cca892c8de5c2653539a19bd9f65568e5e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 19:43:31 +0000 Subject: [PATCH 2/5] class-view: register mailbox / mail_message every_codebook_id_appears_in_class_ids_all requires every minted concept to carry a ClassView; the Mail mint missed it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-class-view/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ogar-class-view/src/lib.rs b/crates/ogar-class-view/src/lib.rs index 4416432f..de44e2c1 100644 --- a/crates/ogar-class-view/src/lib.rs +++ b/crates/ogar-class-view/src/lib.rs @@ -98,6 +98,8 @@ use ogar_vocab::{ knowledge_item, lab_value, machine_memory_map, + mail_message, + mailbox, mars_application, mars_machine, mars_node_template, @@ -232,6 +234,8 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> { // manifest, not promoted into the shared OGAR schema. ("weather_cell", weather_cell()), ("weather_static_cell", weather_static_cell()), + ("mailbox", mailbox()), + ("mail_message", mail_message()), // ── 0x08XX — OCR (container kinds; content stays in content stores) ── ("unicharset", unicharset()), ("recoder", recoder()), From 1f10a5e197b29097f74b622ba8454922f3a03ddc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 19:59:23 +0000 Subject: [PATCH 3/5] vocab: Mail domain is OGIT ogit:Email, not invented concepts Replaces mailbox / mail_message with one concept, email (0x0501), read from OGIT ogit:Email (SGO; optional attributes from EmailCorrespondance). Mailboxes, folders and flags are columns of an email row. SpearPort maps Spear's public names onto canon: Email/Message -> email, Document/DriveItem -> the existing document (0x080B). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-class-view/src/lib.rs | 6 +- crates/ogar-vocab/src/capability_registry.rs | 2 +- crates/ogar-vocab/src/lib.rs | 64 +++++++------------- crates/ogar-vocab/src/ports.rs | 24 +++++--- 4 files changed, 40 insertions(+), 56 deletions(-) diff --git a/crates/ogar-class-view/src/lib.rs b/crates/ogar-class-view/src/lib.rs index de44e2c1..3ba09e9d 100644 --- a/crates/ogar-class-view/src/lib.rs +++ b/crates/ogar-class-view/src/lib.rs @@ -87,6 +87,7 @@ use ogar_vocab::{ currency_policy, diagnosis, document, + email, examination, external_practice, hr_department, @@ -98,8 +99,6 @@ use ogar_vocab::{ knowledge_item, lab_value, machine_memory_map, - mail_message, - mailbox, mars_application, mars_machine, mars_node_template, @@ -234,8 +233,7 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> { // manifest, not promoted into the shared OGAR schema. ("weather_cell", weather_cell()), ("weather_static_cell", weather_static_cell()), - ("mailbox", mailbox()), - ("mail_message", mail_message()), + ("email", email()), // ── 0x08XX — OCR (container kinds; content stays in content stores) ── ("unicharset", unicharset()), ("recoder", recoder()), diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index f9b39c0b..ae1d24af 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -377,7 +377,7 @@ fn resolve_concept_row(id: u16) -> Option<(&'static str, u16)> { mod the_canon_carries_no_palette_rows { #[test] fn no_0x17xx_row_reached_the_globally_mirrored_codebook() { - assert_eq!(crate::class_ids::ALL.len(), 100); + assert_eq!(crate::class_ids::ALL.len(), 99); for (_, id) in crate::class_ids::ALL { assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook"); } diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index aa19ca90..e9e0eba5 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -1106,7 +1106,7 @@ impl Class { /// 0x02XX commerce / ERP (OSB ↔ Odoo cross-curator) /// 0x03XX Ontology (OBO biomedical reference: MONDO/HPO/Uberon/PATO/RO — zero rows here; concepts in ogar-obo) /// 0x04XX Weather / Atmosphere (forecast + atmospheric reference cells) -/// 0x05XX Mail (mailbox + message; consumed by spear / stalwart) +/// 0x05XX Mail (OGIT `ogit:Email`; consumed by spear) /// 0x06XX unassigned /// 0x07XX reserved: OSINT /// 0x08XX OCR (container kinds: unicharset/recoder/charset) @@ -1241,11 +1241,10 @@ const CODEBOOK: &[(&str, u16)] = &[ ("weather_cell", 0x0401), ("weather_static_cell", 0x0402), // ── 0x05XX — Mail domain ── - // The two things a mail store authorizes: the mailbox (whose access follows - // its owner in the directory) and the message inside it. Folders, flags and - // threads are rows and columns of these, not concepts of their own. - ("mailbox", 0x0501), - ("mail_message", 0x0502), + // OGIT `ogit:Email` (SGO; optional attributes from the + // `EmailCorrespondance` namespace). Mailboxes, folders, flags and threads + // are columns of an email row, not concepts of their own. + ("email", 0x0501), // ── 0x07XX — OSINT domain: ZERO vocabulary rows BY DESIGN (operator // ruling 2026-07-02, corrects PR #145's two hallucinated concept mints // `osint_system@0x0700` / `osint_person@0x0701`). Within the OSINT domain @@ -1485,9 +1484,7 @@ pub enum ConceptDomain { /// `0x04XX` — Weather / Atmosphere. Shared atmospheric and forecast-grid /// concepts; public environmental reference data, not an OSM extension. Weather, - /// `0x05XX` — Mail. The mailbox and the message, as one mail store - /// authorizes them; consumed by spear (the mail hub) and the stalwart - /// dir-sim directory. + /// `0x05XX` — Mail. OGIT `ogit:Email`; consumed by spear (the mail hub). Mail, /// `0x07XX` — OSINT (open-source intelligence). Osint, @@ -2064,11 +2061,8 @@ pub mod class_ids { // ── 0x05XX — Mail domain ── - /// `mailbox` (`0x0501`) — one mailbox. Access follows the directory object - /// that owns it. - pub const MAILBOX: u16 = 0x0501; - /// `mail_message` (`0x0502`) — one message in a mailbox. - pub const MAIL_MESSAGE: u16 = 0x0502; + /// `email` (`0x0501`) — one email message. OGIT `ogit:Email`. + pub const EMAIL: u16 = 0x0501; // ── 0x08XX — OCR domain (document extraction; the Tesseract-rs arc) ── // Class-level container KINDS only: the concept slots name the container @@ -2389,8 +2383,7 @@ pub mod class_ids { ("weather_cell", WEATHER_CELL), ("weather_static_cell", WEATHER_STATIC_CELL), // 0x05XX — Mail - ("mailbox", MAILBOX), - ("mail_message", MAIL_MESSAGE), + ("email", EMAIL), // 0x07XX — OSINT: ZERO vocabulary rows BY DESIGN (operator ruling // 2026-07-02; see the CODEBOOK 0x07XX section note). No entries // follow — OGAR vocabulary carries no OSINT concept names. @@ -2526,7 +2519,7 @@ pub mod class_ids { // Pin the number here so a bump is never silent. assert_eq!( ALL.len(), - 100, + 99, "class_ids::ALL count changed — update this pin AND land the \ corresponding row in lance-graph's \ crates/lance-graph-contract/src/ogar_codebook.rs::CODEBOOK \ @@ -3353,8 +3346,7 @@ pub fn all_promoted_classes() -> Vec { // 0x08XX — OCR arm (9 container kinds), in class_ids::ALL order. weather_cell(), weather_static_cell(), - mailbox(), - mail_message(), + email(), unicharset(), recoder(), charset(), @@ -5059,25 +5051,16 @@ pub fn weather_static_cell() -> Class { } // ───────────────────────────────────────────────────────────────────── -// 0x05XX — Mail canonical builders. Identity only; the message columns live -// in the consumer's ClassView (spear's `messages` Lance schema). +// 0x05XX — Mail canonical builders. Identity only; the columns live in the +// consumer's ClassView (spear's `messages` Lance schema). // ───────────────────────────────────────────────────────────────────── -/// `mailbox` (`0x0501`) — one mailbox. +/// `email` (`0x0501`) — one email message. OGIT `ogit:Email`. #[must_use] -pub fn mailbox() -> Class { - let mut c = Class::new("Mailbox"); +pub fn email() -> Class { + let mut c = Class::new("Email"); c.language = Language::Unknown; - c.canonical_concept = Some("mailbox".to_string()); - c -} - -/// `mail_message` (`0x0502`) — one message in a mailbox. -#[must_use] -pub fn mail_message() -> Class { - let mut c = Class::new("MailMessage"); - c.language = Language::Unknown; - c.canonical_concept = Some("mail_message".to_string()); + c.canonical_concept = Some("email".to_string()); c } @@ -7282,13 +7265,12 @@ mod mail_classid_mint_tests { #[test] fn mail_domain_and_codebook_resolve() { - for id in [class_ids::MAILBOX, class_ids::MAIL_MESSAGE] { - assert_eq!(canonical_concept_domain(id), ConceptDomain::Mail); - } - assert_eq!(canonical_concept_id("mailbox"), Some(0x0501)); - assert_eq!(canonical_concept_id("mail_message"), Some(0x0502)); - assert_eq!(mailbox().canonical_id(), Some(class_ids::MAILBOX)); - assert_eq!(mail_message().canonical_id(), Some(class_ids::MAIL_MESSAGE)); + assert_eq!( + canonical_concept_domain(class_ids::EMAIL), + ConceptDomain::Mail + ); + assert_eq!(canonical_concept_id("email"), Some(0x0501)); + assert_eq!(email().canonical_id(), Some(class_ids::EMAIL)); // 0x06 stays unassigned. assert_eq!(canonical_concept_domain(0x0600), ConceptDomain::Unassigned); } diff --git a/crates/ogar-vocab/src/ports.rs b/crates/ogar-vocab/src/ports.rs index 7b68b9f3..4b7c649e 100644 --- a/crates/ogar-vocab/src/ports.rs +++ b/crates/ogar-vocab/src/ports.rs @@ -641,8 +641,9 @@ pub const WEATHERNEXT_ALIASES: &[(&str, u16)] = &[ // ── Spear (the mail hub) port ──────────────────────────────────────── -/// Spear's `PortSpec` over the shared Mail (`0x05XX`) concepts. `0x000A` is -/// Spear's render skin; the mailbox and message meanings stay in the high u16. +/// Spear's `PortSpec`: its public names onto canon concepts — mail onto +/// `email` (`0x0501`, OGIT `ogit:Email`), documents and drive items onto the +/// existing `document` (`0x080B`). `0x000A` is Spear's render skin. pub struct SpearPort; impl PortSpec for SpearPort { @@ -656,8 +657,10 @@ impl PortSpec for SpearPort { /// Spear's names for the shared mail concepts. pub const SPEAR_ALIASES: &[(&str, u16)] = &[ - ("Mailbox", class_ids::MAILBOX), - ("Message", class_ids::MAIL_MESSAGE), + ("Email", class_ids::EMAIL), + ("Message", class_ids::EMAIL), + ("Document", class_ids::DOCUMENT), + ("DriveItem", class_ids::DOCUMENT), ]; #[cfg(test)] @@ -667,12 +670,13 @@ mod tests { #[test] fn spear_classview_composes_canon_high_custom_low() { - assert_eq!(SpearPort::class_id("Mailbox"), Some(0x0501)); - assert_eq!(SpearPort::class_id("Message"), Some(0x0502)); - let mailbox = render_classid(SpearPort::APP_PREFIX, class_ids::MAILBOX); - assert_eq!(mailbox, 0x0501_000A); - assert_eq!(concept_of(mailbox), 0x0501); - assert_eq!(app_of(mailbox), 0x000A); + assert_eq!(SpearPort::class_id("Email"), Some(0x0501)); + assert_eq!(SpearPort::class_id("Message"), Some(0x0501)); + assert_eq!(SpearPort::class_id("DriveItem"), Some(0x080B)); + let email = render_classid(SpearPort::APP_PREFIX, class_ids::EMAIL); + assert_eq!(email, 0x0501_000A); + assert_eq!(concept_of(email), 0x0501); + assert_eq!(app_of(email), 0x000A); } #[test] From da8995d83b4e537a6068047596b738f651ccc8ab Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:04:34 +0000 Subject: [PATCH 4/5] vocab: email moves into the IAM domain (0x0B05); count fuse removed email is OGIT ogit:Email and sits in 0x0BXX above the AuthStore profiles, so no Mail domain is opened. Per HOTPLUG-MIGRATION-GUIDE step 4 the global ALL.len() count pins are deleted: lance-graph-ogar's assert_codebook_parity checks the full bijection, and MirrorDrift reports drift per plug. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-class-view/src/lib.rs | 2 +- crates/ogar-vocab/src/capability_registry.rs | 1 - crates/ogar-vocab/src/lib.rs | 84 +++++++------------- crates/ogar-vocab/src/ports.rs | 10 +-- docs/APP-CLASS-CODEBOOK-LAYOUT.md | 4 +- 5 files changed, 35 insertions(+), 66 deletions(-) diff --git a/crates/ogar-class-view/src/lib.rs b/crates/ogar-class-view/src/lib.rs index 3ba09e9d..31ca56d9 100644 --- a/crates/ogar-class-view/src/lib.rs +++ b/crates/ogar-class-view/src/lib.rs @@ -233,7 +233,6 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> { // manifest, not promoted into the shared OGAR schema. ("weather_cell", weather_cell()), ("weather_static_cell", weather_static_cell()), - ("email", email()), // ── 0x08XX — OCR (container kinds; content stays in content stores) ── ("unicharset", unicharset()), ("recoder", recoder()), @@ -269,6 +268,7 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> { ("auth_zitadel", auth_zitadel()), ("auth_zanzibar", auth_zanzibar()), ("auth_ory_keto", auth_ory_keto()), + ("email", email()), // ── 0x0DXX — HR cluster (closes the final 4-of-11 odoo-rs #14 gap) ── ("hr_employee", hr_employee()), ("hr_department", hr_department()), diff --git a/crates/ogar-vocab/src/capability_registry.rs b/crates/ogar-vocab/src/capability_registry.rs index ae1d24af..8203e4f2 100644 --- a/crates/ogar-vocab/src/capability_registry.rs +++ b/crates/ogar-vocab/src/capability_registry.rs @@ -377,7 +377,6 @@ fn resolve_concept_row(id: u16) -> Option<(&'static str, u16)> { mod the_canon_carries_no_palette_rows { #[test] fn no_0x17xx_row_reached_the_globally_mirrored_codebook() { - assert_eq!(crate::class_ids::ALL.len(), 99); for (_, id) in crate::class_ids::ALL { assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook"); } diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index e9e0eba5..7cb58efa 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -1106,7 +1106,7 @@ impl Class { /// 0x02XX commerce / ERP (OSB ↔ Odoo cross-curator) /// 0x03XX Ontology (OBO biomedical reference: MONDO/HPO/Uberon/PATO/RO — zero rows here; concepts in ogar-obo) /// 0x04XX Weather / Atmosphere (forecast + atmospheric reference cells) -/// 0x05XX Mail (OGIT `ogit:Email`; consumed by spear) +/// 0x05XX unassigned /// 0x06XX unassigned /// 0x07XX reserved: OSINT /// 0x08XX OCR (container kinds: unicharset/recoder/charset) @@ -1240,11 +1240,6 @@ const CODEBOOK: &[(&str, u16)] = &[ // ClassView selection remains in the low u16 of the full classid. ("weather_cell", 0x0401), ("weather_static_cell", 0x0402), - // ── 0x05XX — Mail domain ── - // OGIT `ogit:Email` (SGO; optional attributes from the - // `EmailCorrespondance` namespace). Mailboxes, folders, flags and threads - // are columns of an email row, not concepts of their own. - ("email", 0x0501), // ── 0x07XX — OSINT domain: ZERO vocabulary rows BY DESIGN (operator // ruling 2026-07-02, corrects PR #145's two hallucinated concept mints // `osint_system@0x0700` / `osint_person@0x0701`). Within the OSINT domain @@ -1350,6 +1345,12 @@ const CODEBOOK: &[(&str, u16)] = &[ ("auth_zitadel", 0x0B02), ("auth_zanzibar", 0x0B03), ("auth_ory_keto", 0x0B04), + // `email` (`0x0B05`) — OGIT `ogit:Email` (SGO; optional attributes from + // the `EmailCorrespondance` namespace). Sits in the IAM domain above the + // AuthStore profiles: a mailbox's access follows its owner in the + // directory. Mailboxes, folders, flags and threads are columns of an + // email row, not concepts of their own. Consumed by spear (`SpearPort`). + ("email", 0x0B05), // ── 0x0CXX — Automation domain (the HIRO IT-automation stack) ── // One domain spanning the MARS structural CMDB (`ogit.MARS:` — // Application/Resource/Software/Machine, the A→R→S→M dependsOn backbone) @@ -1484,8 +1485,6 @@ pub enum ConceptDomain { /// `0x04XX` — Weather / Atmosphere. Shared atmospheric and forecast-grid /// concepts; public environmental reference data, not an OSM extension. Weather, - /// `0x05XX` — Mail. OGIT `ogit:Email`; consumed by spear (the mail hub). - Mail, /// `0x07XX` — OSINT (open-source intelligence). Osint, /// `0x08XX` — OCR (optical character recognition / document @@ -1761,7 +1760,7 @@ pub enum ConceptDomain { /// reads the compartment off the key (`classid >> 24`) instead of looking /// it up. Form, - /// Any high-byte slot not yet assigned a domain (`0x06XX`, + /// Any high-byte slot not yet assigned a domain (`0x05XX`–`0x06XX`, /// `0x10XX`–`0x16XX`, `0x18XX`–`0xBFXX`, `0xC2XX`–`0xC3XX`, /// `0xC5XX`, `0xC7XX`+). Unassigned, @@ -1777,7 +1776,6 @@ pub fn canonical_concept_domain(id: u16) -> ConceptDomain { 0x02 => ConceptDomain::Commerce, 0x03 => ConceptDomain::Ontology, 0x04 => ConceptDomain::Weather, - 0x05 => ConceptDomain::Mail, 0x07 => ConceptDomain::Osint, 0x08 => ConceptDomain::Ocr, 0x09 => ConceptDomain::Health, @@ -2059,11 +2057,6 @@ pub mod class_ids { /// geography-derived weather context, distinct from dynamic fields. pub const WEATHER_STATIC_CELL: u16 = 0x0402; - // ── 0x05XX — Mail domain ── - - /// `email` (`0x0501`) — one email message. OGIT `ogit:Email`. - pub const EMAIL: u16 = 0x0501; - // ── 0x08XX — OCR domain (document extraction; the Tesseract-rs arc) ── // Class-level container KINDS only: the concept slots name the container // types the Core resolves — never their content. The 112 unichars of a @@ -2215,6 +2208,9 @@ pub mod class_ids { pub const AUTH_ZANZIBAR: u16 = 0x0B03; /// `auth_ory_keto` (`0x0B04`) — Ory Keto provider profile. pub const AUTH_ORY_KETO: u16 = 0x0B04; + /// `email` (`0x0B05`) — one email message. OGIT `ogit:Email`. In the IAM + /// domain, above the AuthStore profiles. + pub const EMAIL: u16 = 0x0B05; // ── 0x0DXX — HR domain (employment / org / contracts) ── @@ -2382,8 +2378,6 @@ pub mod class_ids { // 0x04XX — Weather / Atmosphere ("weather_cell", WEATHER_CELL), ("weather_static_cell", WEATHER_STATIC_CELL), - // 0x05XX — Mail - ("email", EMAIL), // 0x07XX — OSINT: ZERO vocabulary rows BY DESIGN (operator ruling // 2026-07-02; see the CODEBOOK 0x07XX section note). No entries // follow — OGAR vocabulary carries no OSINT concept names. @@ -2422,6 +2416,7 @@ pub mod class_ids { ("auth_zitadel", AUTH_ZITADEL), ("auth_zanzibar", AUTH_ZANZIBAR), ("auth_ory_keto", AUTH_ORY_KETO), + ("email", EMAIL), // 0x0DXX — HR (employment / org / contracts; closes the final // 4-of-11 cross-axis gap from odoo-rs PR #14) ("hr_employee", HR_EMPLOYEE), @@ -2502,32 +2497,6 @@ pub mod class_ids { } } - #[test] - fn count_fuse_matches_lance_graph_ogar_mirror() { - // OGAR-side half of a two-sided drift check. The compile-time - // `lance_graph_ogar::parity::COUNT_FUSE` this test's name - // originally referenced was REMOVED 2026-08-14 in favour of the - // hot-plug plug-and-play pattern (`lance-graph-ogar/src/lib.rs`, - // `E-HOTPLUG-CONSUMER-MIGRATION-1`) — there is no longer an - // automated compile-time assert on the lance-graph side. - // `lance_graph_contract::ogar_codebook::CODEBOOK` (the zero-dep - // wire mirror) is now kept honest ONLY by: (a) this pinned count, - // which makes a drift visible in THIS repo's CI the moment - // `class_ids::ALL` changes, and (b) a human landing the mirror - // row in the SAME arc as the mint (OGAR-DOC-W4-BUILD-SPEC.md - // §W4 capstone gate G17/G19) — there is no third safety net. - // Pin the number here so a bump is never silent. - assert_eq!( - ALL.len(), - 99, - "class_ids::ALL count changed — update this pin AND land the \ - corresponding row in lance-graph's \ - crates/lance-graph-contract/src/ogar_codebook.rs::CODEBOOK \ - mirror (COUNT_FUSE was removed 2026-08-14; nothing else will \ - catch a stale mirror) in the same PR-arc", - ); - } - #[test] fn divergent_curator_names_share_one_constant() { // The whole point of the codebook, in code: a Redmine Issue @@ -3346,7 +3315,6 @@ pub fn all_promoted_classes() -> Vec { // 0x08XX — OCR arm (9 container kinds), in class_ids::ALL order. weather_cell(), weather_static_cell(), - email(), unicharset(), recoder(), charset(), @@ -3383,6 +3351,7 @@ pub fn all_promoted_classes() -> Vec { auth_zitadel(), auth_zanzibar(), auth_ory_keto(), + email(), // 0x0DXX — HR arm hr_employee(), hr_department(), @@ -5051,11 +5020,11 @@ pub fn weather_static_cell() -> Class { } // ───────────────────────────────────────────────────────────────────── -// 0x05XX — Mail canonical builders. Identity only; the columns live in the +// `email` (`0x0B05`, IAM domain). Identity only; the columns live in the // consumer's ClassView (spear's `messages` Lance schema). // ───────────────────────────────────────────────────────────────────── -/// `email` (`0x0501`) — one email message. OGIT `ogit:Email`. +/// `email` (`0x0B05`) — one email message. OGIT `ogit:Email`. #[must_use] pub fn email() -> Class { let mut c = Class::new("Email"); @@ -6080,11 +6049,10 @@ mod tests { // lives in ogar-obo; plug-and-play, never pulls into ERP consumers). assert_eq!(canonical_concept_domain(0x0300), ConceptDomain::Ontology); assert_eq!(canonical_concept_domain(0x03AB), ConceptDomain::Ontology); - // Weather / Atmosphere block (0x04), Mail (0x05), then still-unassigned 0x06. + // Weather / Atmosphere block (0x04), then still-unassigned 0x05-0x06. assert_eq!(canonical_concept_domain(0x0400), ConceptDomain::Weather); assert_eq!(canonical_concept_domain(0x0401), ConceptDomain::Weather); - assert_eq!(canonical_concept_domain(0x0500), ConceptDomain::Mail); - assert_eq!(canonical_concept_domain(0x05FF), ConceptDomain::Mail); + assert_eq!(canonical_concept_domain(0x0500), ConceptDomain::Unassigned); assert_eq!(canonical_concept_domain(0x0600), ConceptDomain::Unassigned); // HR block (0x0D). assert_eq!(canonical_concept_domain(0x0D00), ConceptDomain::HR); @@ -6153,6 +6121,7 @@ mod tests { ("auth_zitadel", 0x0B02), ("auth_zanzibar", 0x0B03), ("auth_ory_keto", 0x0B04), + ("email", 0x0B05), ] { assert_eq!( canonical_concept_id(concept), @@ -6161,8 +6130,8 @@ mod tests { ); assert_eq!(canonical_concept_domain(id), ConceptDomain::Auth); } - // The four preminted profiles are the whole Auth block today. - assert_eq!(concepts_in_domain(ConceptDomain::Auth).count(), 4); + // The four preminted profiles plus `email` are the Auth block today. + assert_eq!(concepts_in_domain(ConceptDomain::Auth).count(), 5); } #[test] @@ -6313,7 +6282,7 @@ mod tests { assert_eq!(concepts_in_domain(ConceptDomain::Commerce).count(), 11); assert_eq!(concepts_in_domain(ConceptDomain::ProjectMgmt).count(), 26); assert_eq!(concepts_in_domain(ConceptDomain::Anatomy).count(), 4); - assert_eq!(concepts_in_domain(ConceptDomain::Auth).count(), 4); + assert_eq!(concepts_in_domain(ConceptDomain::Auth).count(), 5); assert_eq!(concepts_in_domain(ConceptDomain::Automation).count(), 9); // Every yielded Automation id really is in-domain (0x0CXX). let automation: Vec<&str> = concepts_in_domain(ConceptDomain::Automation) @@ -7264,14 +7233,15 @@ mod mail_classid_mint_tests { use super::*; #[test] - fn mail_domain_and_codebook_resolve() { + fn email_sits_in_the_iam_domain_above_the_auth_profiles() { assert_eq!( canonical_concept_domain(class_ids::EMAIL), - ConceptDomain::Mail + ConceptDomain::Auth ); - assert_eq!(canonical_concept_id("email"), Some(0x0501)); + assert!(class_ids::EMAIL > class_ids::AUTH_ORY_KETO); + assert_eq!(canonical_concept_id("email"), Some(0x0B05)); assert_eq!(email().canonical_id(), Some(class_ids::EMAIL)); - // 0x06 stays unassigned. - assert_eq!(canonical_concept_domain(0x0600), ConceptDomain::Unassigned); + // No Mail domain was opened for it. + assert_eq!(canonical_concept_domain(0x0501), ConceptDomain::Unassigned); } } diff --git a/crates/ogar-vocab/src/ports.rs b/crates/ogar-vocab/src/ports.rs index 4b7c649e..16f504b3 100644 --- a/crates/ogar-vocab/src/ports.rs +++ b/crates/ogar-vocab/src/ports.rs @@ -642,7 +642,7 @@ pub const WEATHERNEXT_ALIASES: &[(&str, u16)] = &[ // ── Spear (the mail hub) port ──────────────────────────────────────── /// Spear's `PortSpec`: its public names onto canon concepts — mail onto -/// `email` (`0x0501`, OGIT `ogit:Email`), documents and drive items onto the +/// `email` (`0x0B05`, OGIT `ogit:Email`), documents and drive items onto the /// existing `document` (`0x080B`). `0x000A` is Spear's render skin. pub struct SpearPort; @@ -670,12 +670,12 @@ mod tests { #[test] fn spear_classview_composes_canon_high_custom_low() { - assert_eq!(SpearPort::class_id("Email"), Some(0x0501)); - assert_eq!(SpearPort::class_id("Message"), Some(0x0501)); + assert_eq!(SpearPort::class_id("Email"), Some(0x0B05)); + assert_eq!(SpearPort::class_id("Message"), Some(0x0B05)); assert_eq!(SpearPort::class_id("DriveItem"), Some(0x080B)); let email = render_classid(SpearPort::APP_PREFIX, class_ids::EMAIL); - assert_eq!(email, 0x0501_000A); - assert_eq!(concept_of(email), 0x0501); + assert_eq!(email, 0x0B05_000A); + assert_eq!(concept_of(email), 0x0B05); assert_eq!(app_of(email), 0x000A); } diff --git a/docs/APP-CLASS-CODEBOOK-LAYOUT.md b/docs/APP-CLASS-CODEBOOK-LAYOUT.md index da98ce3d..ecc3821e 100644 --- a/docs/APP-CLASS-CODEBOOK-LAYOUT.md +++ b/docs/APP-CLASS-CODEBOOK-LAYOUT.md @@ -171,7 +171,7 @@ materialised until the app mints its first private class). | `lo u16` | App / namespace | Core domain(s) it consumes | Private codebook today? | |---|---|---|---| -| `0x0000` | **Shared canonical core** | all (`0x01/02/04/05/07/08/09` + `0x0A` anatomy + `0x0B` auth + `0x0C` automation) | n/a (this *is* core) | +| `0x0000` | **Shared canonical core** | all (`0x01/02/04/07/08/09` + `0x0A` anatomy + `0x0B` auth + `0x0C` automation) | n/a (this *is* core) | | `0x0001` | OpenProject (openproject-nexgen-rs) | `0x01` project-mgmt | **no** — maps onto core | | `0x0002` | Odoo | `0x02` commerce | **no** — maps onto core (converge `od-ontology`) | | `0x0003` | WoA / woa-rs | `0x02` commerce (work orders) | **no** — maps onto core | @@ -181,7 +181,7 @@ materialised until the app mints its first private class). | `0x0007` | Redmine | `0x01` project-mgmt | **no** — same concepts as OpenProject, own templates | | `0x0008` | OpenStreetMap (openstreetmap-website-rs) | `0x0F` geo | **no** — maps entirely onto core | | `0x0009` | WeatherNext / weathernext-rs | `0x04` weather / atmosphere | **no** — maps onto core | -| `0x000A` | Spear (mail hub) | `0x05` mail | **no** — maps onto core | +| `0x000A` | Spear (mail hub) | `0x0B` IAM (`email` `0x0B05`) + `0x08` (`document`) | **no** — maps onto core | | `0x00A0` | (reserved) future app block | — | — | > **OpenProject (`0x0001`) and Redmine (`0x0007`) are the showcase:** From 51f5155eb34a0d47b52cafdafea6f5de27811f63 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 20:18:08 +0000 Subject: [PATCH 5/5] vocab: const-assert the email ordering (clippy assertions_on_constants) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg --- crates/ogar-vocab/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ogar-vocab/src/lib.rs b/crates/ogar-vocab/src/lib.rs index 7cb58efa..2c95c523 100644 --- a/crates/ogar-vocab/src/lib.rs +++ b/crates/ogar-vocab/src/lib.rs @@ -7238,7 +7238,7 @@ mod mail_classid_mint_tests { canonical_concept_domain(class_ids::EMAIL), ConceptDomain::Auth ); - assert!(class_ids::EMAIL > class_ids::AUTH_ORY_KETO); + const { assert!(class_ids::EMAIL > class_ids::AUTH_ORY_KETO) }; assert_eq!(canonical_concept_id("email"), Some(0x0B05)); assert_eq!(email().canonical_id(), Some(class_ids::EMAIL)); // No Mail domain was opened for it.