Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/ogar-class-view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ use ogar_vocab::{
currency_policy,
diagnosis,
document,
email,
examination,
external_practice,
hr_department,
Expand Down Expand Up @@ -267,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()),
Expand Down
1 change: 0 additions & 1 deletion crates/ogar-vocab/src/capability_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(), 98);
for (_, id) in crate::class_ids::ALL {
assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook");
}
Expand Down
76 changes: 47 additions & 29 deletions crates/ogar-vocab/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,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)
Expand Down Expand Up @@ -2202,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) ──

Expand Down Expand Up @@ -2407,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),
Expand Down Expand Up @@ -2487,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(),
98,
"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
Expand Down Expand Up @@ -3367,6 +3351,7 @@ pub fn all_promoted_classes() -> Vec<Class> {
auth_zitadel(),
auth_zanzibar(),
auth_ory_keto(),
email(),
// 0x0DXX — HR arm
hr_employee(),
hr_department(),
Expand Down Expand Up @@ -5034,6 +5019,20 @@ pub fn weather_static_cell() -> Class {
c
}

// ─────────────────────────────────────────────────────────────────────
// `email` (`0x0B05`, IAM domain). Identity only; the columns live in the
// consumer's ClassView (spear's `messages` Lance schema).
// ─────────────────────────────────────────────────────────────────────

/// `email` (`0x0B05`) — one email message. OGIT `ogit:Email`.
#[must_use]
pub fn email() -> Class {
let mut c = Class::new("Email");
c.language = Language::Unknown;
c.canonical_concept = Some("email".to_string());
c
}

// ─────────────────────────────────────────────────────────────────────
// 0xC6XX — MMIO hardware-platform builders (C64 = founding tenant).
// Kind-level containers only: registers/opcodes are content rows.
Expand Down Expand Up @@ -6122,6 +6121,7 @@ mod tests {
("auth_zitadel", 0x0B02),
("auth_zanzibar", 0x0B03),
("auth_ory_keto", 0x0B04),
("email", 0x0B05),
] {
assert_eq!(
canonical_concept_id(concept),
Expand All @@ -6130,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]
Expand Down Expand Up @@ -6282,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)
Expand Down Expand Up @@ -7227,3 +7227,21 @@ mod weather_classid_mint_tests {
);
}
}

#[cfg(test)]
mod mail_classid_mint_tests {
use super::*;

#[test]
fn email_sits_in_the_iam_domain_above_the_auth_profiles() {
assert_eq!(
canonical_concept_domain(class_ids::EMAIL),
ConceptDomain::Auth
);
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.
assert_eq!(canonical_concept_domain(0x0501), ConceptDomain::Unassigned);
}
}
35 changes: 35 additions & 0 deletions crates/ogar-vocab/src/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,11 +639,46 @@ pub const WEATHERNEXT_ALIASES: &[(&str, u16)] = &[
("WeatherStaticCell", class_ids::WEATHER_STATIC_CELL),
];

// ── Spear (the mail hub) port ────────────────────────────────────────

/// Spear's `PortSpec`: its public names onto canon concepts — mail onto
/// `email` (`0x0B05`, 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 {
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)] = &[
("Email", class_ids::EMAIL),
("Message", class_ids::EMAIL),
("Document", class_ids::DOCUMENT),
("DriveItem", class_ids::DOCUMENT),
];

#[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("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, 0x0B05_000A);
assert_eq!(concept_of(email), 0x0B05);
assert_eq!(app_of(email), 0x000A);
}

#[test]
fn weathernext_classview_composes_canon_high_custom_low() {
assert_eq!(WeatherNextPort::APP_PREFIX, 0x0009);
Expand Down
1 change: 1 addition & 0 deletions docs/APP-CLASS-CODEBOOK-LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | `0x0B` IAM (`email` `0x0B05`) + `0x08` (`document`) | **no** — maps onto core |
| `0x00A0` | (reserved) future app block | — | — |

> **OpenProject (`0x0001`) and Redmine (`0x0007`) are the showcase:**
Expand Down
Loading