Skip to content
Closed
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: 1 addition & 1 deletion crates/lance-graph-ogar/src/bridges/medcare_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use crate::bridges::unified::UnifiedBridge;
// `HealthcarePort::NAMESPACE` / `::aliases()` are `PortSpec` associated
// items — the trait must be in scope for the resolution to work (codex
// P1 on PR #570). Same import in the test module below.
use ogar_vocab::ports::PortSpec;
pub use ogar_vocab::ports::HealthcarePort;
use ogar_vocab::ports::PortSpec;

/// MedCare `NamespaceBridge` — alias over the generic harness, locked to
/// the `Healthcare` namespace via [`HealthcarePort`].
Expand Down
29 changes: 29 additions & 0 deletions crates/lance-graph-ogar/src/bridges/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,38 @@
//! — locks to the `Healthcare` namespace. `Patient` / `Diagnosis` /
//! `LabValue` / `Medication` / `Treatment` / `Visit` / `VitalSign`
//! resolve to the `0x09XX` Health codebook (Northstar T9).
//! - [`WoaBridge`]: `UnifiedBridge<ogar_vocab::ports::WoaPort>` — locks
//! to the `WorkOrder` namespace. WoA's German/English commerce names
//! (`Vorgang` / `Kunde` / `Rechnung` / …) resolve to the `0x02XX`
//! commerce block, and `Stundenzettel` / `TimeEntry` resolve to the
//! SAME `BILLABLE_WORK_ENTRY` (`0x0103`) as the planner ports — the
//! cross-fork convergence pin (OGAR #93).
//! - [`SmbBridge`]: `UnifiedBridge<ogar_vocab::ports::SmbPort>` — locks
//! to the `SMB` namespace. Sister of [`WoaBridge`]: SMB's `Kunde` /
//! `Auftrag` / `Stundenzettel` resolve to the SAME canonical class_ids
//! as the WoA equivalents (OGAR #93).
//! - [`OdooBridge`]: `UnifiedBridge<ogar_vocab::ports::OdooPort>` — locks
//! to the `Odoo` namespace. Odoo model names (`account.move` /
//! `res.partner` / …) resolve to the `0x02XX` commerce block, and the
//! cross-arm `account.analytic.line` resolves to `BILLABLE_WORK_ENTRY`
//! (`0x0103`) — the commerce-arm convergence pin (OGAR #94).

pub mod unified;

mod medcare_bridge;
mod odoo_bridge;
mod openproject_bridge;
mod redmine_bridge;
mod smb_bridge;
mod woa_bridge;

pub use medcare_bridge::{HealthcarePort, MedcareBridge};
pub use odoo_bridge::{OdooBridge, OdooPort};
pub use openproject_bridge::{OpenProjectBridge, OpenProjectPort};
pub use redmine_bridge::{RedmineBridge, RedminePort};
pub use smb_bridge::{SmbBridge, SmbPort};
pub use unified::UnifiedBridge;
pub use woa_bridge::{WoaBridge, WoaPort};

// Compatibility shims for the pre-migration constants. `bridges`
// previously re-exported `OPENPROJECT_CODEBOOK` / `REDMINE_CODEBOOK`
Expand All @@ -56,3 +77,11 @@ pub use unified::UnifiedBridge;
pub use openproject_bridge::OPENPROJECT_CODEBOOK;
#[allow(deprecated)]
pub use redmine_bridge::REDMINE_CODEBOOK;
// The OGAR-port bridges added for #93/#94 expose the same `*_ALIASES`
// constants, so they get the matching `*_CODEBOOK` shim for symmetry.
#[allow(deprecated)]
pub use odoo_bridge::ODOO_CODEBOOK;
#[allow(deprecated)]
pub use smb_bridge::SMB_CODEBOOK;
#[allow(deprecated)]
pub use woa_bridge::WOA_CODEBOOK;
78 changes: 78 additions & 0 deletions crates/lance-graph-ogar/src/bridges/odoo_bridge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Odoo (odoo-rs) tenant bridge — a thin type alias over
//! [`crate::bridges::unified::UnifiedBridge`] parameterised by
//! [`ogar_vocab::ports::OdooPort`].
//!
//! The differences between bridges (namespace, bridge_id, public-name
//! → class_id alias table) all come from the OGAR class schema. The
//! `OdooPort` carries the `Odoo` namespace + `odoo` bridge_id + the
//! Odoo-model alias table (`account.move` → COMMERCIAL_DOCUMENT,
//! `res.partner` → BILLING_PARTY, …) plus the cross-arm bridge
//! `account.analytic.line` → `BILLABLE_WORK_ENTRY` (`0x0103`), the SAME
//! canonical id the OpenProject / Redmine `TimeEntry` ports resolve to —
//! the commerce-arm convergence pin (OGAR #94, Northstar §7 T10).

use crate::bridges::unified::UnifiedBridge;
// `OdooPort::NAMESPACE` / `::aliases()` are `PortSpec` associated items —
// the trait must be in scope for the resolution to work (codex P1 on
// PR #570). Same import in the test module below.
pub use ogar_vocab::ports::OdooPort;
use ogar_vocab::ports::PortSpec;

/// Odoo `NamespaceBridge` — alias over the generic harness, locked to the
/// `Odoo` namespace via [`OdooPort`].
pub type OdooBridge = UnifiedBridge<OdooPort>;

/// Canonical namespace name for Odoo. Mirrors `OdooPort::NAMESPACE` so
/// consumers that import the constant from this module keep building.
pub const NAMESPACE: &str = OdooPort::NAMESPACE;

/// Compatibility shim — re-exports `ogar_vocab::ports::ODOO_ALIASES`
/// under a `*_CODEBOOK` name for symmetry with the other per-port
/// bridges. New code should reach for `ogar_vocab::ports::ODOO_ALIASES`
/// (or `OdooPort::aliases()`) directly — going through the canonical
/// layer keeps lance-graph free of port-specific data.
#[deprecated(
note = "use `ogar_vocab::ports::ODOO_ALIASES` (or `OdooPort::aliases()`) — the constant lives in OGAR"
)]
pub const ODOO_CODEBOOK: &[(&str, u16)] = ogar_vocab::ports::ODOO_ALIASES;

#[cfg(test)]
mod tests {
use super::*;
use ogar_vocab::class_ids;
// PortSpec needed in scope for `OdooPort::aliases()` / `::class_id()`
// (the methods are trait items — codex P1 on PR #570).
use ogar_vocab::ports::PortSpec;

#[test]
fn namespace_and_bridge_id_mirror_the_port() {
assert_eq!(NAMESPACE, "Odoo");
assert_eq!(OdooPort::NAMESPACE, "Odoo");
assert_eq!(OdooPort::BRIDGE_ID, "odoo");
}

#[test]
fn port_resolves_account_move_to_commercial_document() {
assert_eq!(
OdooPort::class_id("account.move"),
Some(class_ids::COMMERCIAL_DOCUMENT)
);
assert_eq!(OdooPort::class_id("account.move"), Some(0x0202));
}

#[test]
fn port_analytic_line_converges_on_billable_work_entry() {
// The cross-arm bridge (OGAR #94): Odoo's timesheet/cost line
// resolves to the SAME canonical id as the planner ports.
assert_eq!(
OdooPort::class_id("account.analytic.line"),
Some(class_ids::BILLABLE_WORK_ENTRY)
);
assert_eq!(OdooPort::class_id("account.analytic.line"), Some(0x0103));
}

#[test]
fn port_returns_none_for_non_codebook_name() {
assert_eq!(OdooPort::class_id("not.a.model"), None);
}
}
2 changes: 1 addition & 1 deletion crates/lance-graph-ogar/src/bridges/openproject_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::bridges::unified::UnifiedBridge;
// `OpenProjectPort::NAMESPACE` / `::aliases()` are `PortSpec`
// associated items — the trait must be in scope for the resolution to
// work (codex P1 on PR #570). Same import in the test module below.
use ogar_vocab::ports::PortSpec;
pub use ogar_vocab::ports::OpenProjectPort;
use ogar_vocab::ports::PortSpec;

/// OpenProject `NamespaceBridge` — alias over the generic harness.
pub type OpenProjectBridge = UnifiedBridge<OpenProjectPort>;
Expand Down
76 changes: 76 additions & 0 deletions crates/lance-graph-ogar/src/bridges/smb_bridge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! SMB (small-and-medium-business German office ERP) tenant bridge — a
//! thin type alias over [`crate::bridges::unified::UnifiedBridge`]
//! parameterised by [`ogar_vocab::ports::SmbPort`].
//!
//! The differences between bridges (namespace, bridge_id, public-name
//! → class_id alias table) all come from the OGAR class schema. The
//! `SmbPort` carries the `SMB` namespace + `smb` bridge_id + the
//! German/English alias table (Kunde ≡ Customer, Auftrag ≡ Order,
//! Rechnung ≡ Invoice, Stundenzettel ≡ TimeEntry, …). Sister of
//! [`super::woa_bridge::WoaBridge`]: SMB's `Stundenzettel` resolves to
//! the SAME canonical `BILLABLE_WORK_ENTRY` (`0x0103`) as the WoA and
//! planner ports — cross-fork convergence (OGAR #93).

use crate::bridges::unified::UnifiedBridge;
// `SmbPort::NAMESPACE` / `::aliases()` are `PortSpec` associated items —
// the trait must be in scope for the resolution to work (codex P1 on
// PR #570). Same import in the test module below.
use ogar_vocab::ports::PortSpec;
pub use ogar_vocab::ports::SmbPort;

/// SMB `NamespaceBridge` — alias over the generic harness, locked to the
/// `SMB` namespace via [`SmbPort`].
pub type SmbBridge = UnifiedBridge<SmbPort>;

/// Canonical namespace name for SMB. Mirrors `SmbPort::NAMESPACE` so
/// consumers that import the constant from this module keep building.
pub const NAMESPACE: &str = SmbPort::NAMESPACE;

/// Compatibility shim — re-exports `ogar_vocab::ports::SMB_ALIASES` under
/// a `*_CODEBOOK` name for symmetry with the other per-port bridges. New
/// code should reach for `ogar_vocab::ports::SMB_ALIASES` (or
/// `SmbPort::aliases()`) directly — going through the canonical layer
/// keeps lance-graph free of port-specific data.
#[deprecated(
note = "use `ogar_vocab::ports::SMB_ALIASES` (or `SmbPort::aliases()`) — the constant lives in OGAR"
)]
pub const SMB_CODEBOOK: &[(&str, u16)] = ogar_vocab::ports::SMB_ALIASES;

#[cfg(test)]
mod tests {
use super::*;
use ogar_vocab::class_ids;
// PortSpec needed in scope for `SmbPort::aliases()` / `::class_id()`
// (the methods are trait items — codex P1 on PR #570).
use ogar_vocab::ports::PortSpec;

#[test]
fn namespace_and_bridge_id_mirror_the_port() {
assert_eq!(NAMESPACE, "SMB");
assert_eq!(SmbPort::NAMESPACE, "SMB");
assert_eq!(SmbPort::BRIDGE_ID, "smb");
}

#[test]
fn port_resolves_kunde_to_billing_party() {
assert_eq!(SmbPort::class_id("Kunde"), Some(class_ids::BILLING_PARTY));
assert_eq!(SmbPort::class_id("Kunde"), Some(0x0204));
// English synonym collapses to the same id.
assert_eq!(SmbPort::class_id("Customer"), Some(0x0204));
}

#[test]
fn port_resolves_stundenzettel_to_billable_work_entry() {
// Same convergence pin as WoA (OGAR #93).
assert_eq!(
SmbPort::class_id("Stundenzettel"),
Some(class_ids::BILLABLE_WORK_ENTRY)
);
assert_eq!(SmbPort::class_id("Stundenzettel"), Some(0x0103));
}

#[test]
fn port_returns_none_for_non_codebook_name() {
assert_eq!(SmbPort::class_id("NotAConcept"), None);
}
}
18 changes: 9 additions & 9 deletions crates/lance-graph-ogar/src/bridges/unified.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ impl<P: PortSpec> UnifiedBridge<P> {
/// on the convergence contract.
fn synthesize_codebook_entity(&self, class_id: u16) -> EntityRef {
let ctx_id = NamespaceRegistry::seed_context_id(P::NAMESPACE).unwrap_or(0);
let schema_ptr = SchemaPtr::new(self.g_lock, class_id, SchemaKind::Entity)
.with_context_id(ctx_id);
let schema_ptr =
SchemaPtr::new(self.g_lock, class_id, SchemaKind::Entity).with_context_id(ctx_id);
EntityRef { schema_ptr }
}
}
Expand Down Expand Up @@ -144,13 +144,13 @@ impl<P: PortSpec> NamespaceBridge for UnifiedBridge<P> {
}
}
}
let ptr = self
.registry()
.resolve_uri(uri.as_str())
.ok_or_else(|| BridgeError::NotInScope {
bridge_id: self.bridge_id_static(),
public_name: uri.as_str().to_string(),
})?;
let ptr =
self.registry()
.resolve_uri(uri.as_str())
.ok_or_else(|| BridgeError::NotInScope {
bridge_id: self.bridge_id_static(),
public_name: uri.as_str().to_string(),
})?;
if ptr.namespace_id() != self.g_lock() {
return Err(BridgeError::CrossNamespaceLeak {
bridge_id: self.bridge_id_static(),
Expand Down
71 changes: 71 additions & 0 deletions crates/lance-graph-ogar/src/bridges/woa_bridge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! WoA (Work Order Application) tenant bridge — a thin type alias over
//! [`crate::bridges::unified::UnifiedBridge`] parameterised by
//! [`ogar_vocab::ports::WoaPort`].
//!
//! The differences between bridges (namespace, bridge_id, public-name
//! → class_id alias table) all come from the OGAR class schema. The
//! `WoaPort` carries the `WorkOrder` namespace + `woa` bridge_id + the
//! German/English alias table (Vorgang ≡ WorkOrder, Stundenzettel ≡
//! TimesheetActivity ≡ TimeEntry, Kunde ≡ Customer, …), so this bridge
//! is one line. `Stundenzettel` / `TimeEntry` resolve to the SAME
//! canonical `BILLABLE_WORK_ENTRY` (`0x0103`) as the OpenProject /
//! Redmine planner ports — the cross-fork convergence pin (OGAR #93).

use crate::bridges::unified::UnifiedBridge;
// `WoaPort::NAMESPACE` / `::aliases()` are `PortSpec` associated items —
// the trait must be in scope for the resolution to work (codex P1 on
// PR #570). Same import in the test module below.
use ogar_vocab::ports::PortSpec;
pub use ogar_vocab::ports::WoaPort;

/// WoA `NamespaceBridge` — alias over the generic harness, locked to the
/// `WorkOrder` namespace via [`WoaPort`].
pub type WoaBridge = UnifiedBridge<WoaPort>;

/// Canonical namespace name for WoA. Mirrors `WoaPort::NAMESPACE` so
/// consumers that import the constant from this module keep building.
pub const NAMESPACE: &str = WoaPort::NAMESPACE;

/// Compatibility shim — re-exports `ogar_vocab::ports::WOA_ALIASES` under
/// a `*_CODEBOOK` name for symmetry with the other per-port bridges. New
/// code should reach for `ogar_vocab::ports::WOA_ALIASES` (or
/// `WoaPort::aliases()`) directly — going through the canonical layer
/// keeps lance-graph free of port-specific data.
#[deprecated(
note = "use `ogar_vocab::ports::WOA_ALIASES` (or `WoaPort::aliases()`) — the constant lives in OGAR"
)]
pub const WOA_CODEBOOK: &[(&str, u16)] = ogar_vocab::ports::WOA_ALIASES;

#[cfg(test)]
mod tests {
use super::*;
use ogar_vocab::class_ids;
// PortSpec needed in scope for `WoaPort::aliases()` / `::class_id()`
// (the methods are trait items — codex P1 on PR #570).
use ogar_vocab::ports::PortSpec;

#[test]
fn namespace_and_bridge_id_mirror_the_port() {
assert_eq!(NAMESPACE, "WorkOrder");
assert_eq!(WoaPort::NAMESPACE, "WorkOrder");
assert_eq!(WoaPort::BRIDGE_ID, "woa");
}

#[test]
fn port_resolves_stundenzettel_to_billable_work_entry() {
// The cross-fork convergence pin (OGAR #93): WoA's billable-hours
// concept resolves to the SAME canonical id as the planner ports.
assert_eq!(
WoaPort::class_id("Stundenzettel"),
Some(class_ids::BILLABLE_WORK_ENTRY)
);
assert_eq!(WoaPort::class_id("Stundenzettel"), Some(0x0103));
// English synonym collapses to the same id.
assert_eq!(WoaPort::class_id("TimeEntry"), Some(0x0103));
}

#[test]
fn port_returns_none_for_non_codebook_name() {
assert_eq!(WoaPort::class_id("NotAConcept"), None);
}
}
4 changes: 2 additions & 2 deletions crates/lance-graph-ogar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ pub use ogar_vocab::Class;
pub mod bridges;

pub use bridges::{
HealthcarePort, MedcareBridge, OpenProjectBridge, OpenProjectPort, RedmineBridge, RedminePort,
UnifiedBridge,
HealthcarePort, MedcareBridge, OdooBridge, OdooPort, OpenProjectBridge, OpenProjectPort,
RedmineBridge, RedminePort, SmbBridge, SmbPort, UnifiedBridge, WoaBridge, WoaPort,
};

/// Codebook parity-guard — the drift fuse between OGAR's authoritative codebook
Expand Down
7 changes: 5 additions & 2 deletions crates/lance-graph-ogar/tests/bridge_codebook_convergence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
//! `lance_graph_ontology::bridges::codebook::*` constants.

use lance_graph_ogar::bridges::{OpenProjectBridge, RedmineBridge};
use ogar_vocab::class_ids as codebook;
use lance_graph_ontology::{NamespaceBridge, OntologyRegistry};
use ogar_vocab::class_ids as codebook;
use std::fs;
use std::sync::Arc;

Expand Down Expand Up @@ -129,7 +129,10 @@ fn status_and_type_alias_pairs_converge_through_each_ports_naming() {

assert_eq!(
op.entity("Status").unwrap().schema_ptr.entity_type_id(),
rm.entity("IssueStatus").unwrap().schema_ptr.entity_type_id(),
rm.entity("IssueStatus")
.unwrap()
.schema_ptr
.entity_type_id(),
);
assert_eq!(
op.entity("Status").unwrap().schema_ptr.entity_type_id(),
Expand Down
Loading