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 @@ -112,6 +112,7 @@ use ogar_vocab::{
osm_note,
osm_relation,
osm_relation_member,
osm_street_node,
osm_user,
osm_way,
osm_way_node,
Expand Down Expand Up @@ -266,6 +267,7 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> {
("osm_note", osm_note()),
("osm_gpx_trace", osm_gpx_trace()),
("osm_user", osm_user()),
("osm_street_node", osm_street_node()),
]
}

Expand Down
43 changes: 39 additions & 4 deletions crates/ogar-osm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,34 @@ pub const fn classid(concept: u16, classview: u16) -> u32 {
render_classid(classview, concept)
}

/// Concepts DERIVED by a bake rather than mirrored from a Rails class.
///
/// The Geo domain began as a mirror of OSM's own schema, so "every concept
/// grounds to a Rails class" held by construction. It stopped holding when the
/// bake started synthesizing junctions from the routable-way graph: OSM has no
/// junction element, and inventing a Rails name to satisfy the check would put
/// a falsehood in the Core to keep a test green.
///
/// Membership is therefore an explicit, reviewable list — not a relaxed
/// assertion. Adding a name here is a claim that OSM genuinely has no source
/// element for it.
pub const SYNTHESIZED: &[&str] = &["osm_street_node"];

/// Every Geo concept, in codebook order, with its Rails source class(es).
///
/// The `Old*` versioned Rails classes ground to their base concept — the
/// temporal axis is a Lance version, not a new id — which is why `Node` and
/// `OldNode` share `0x0F01`.
///
/// One entry has NO Rails source: see [`SYNTHESIZED`].
pub const OSM_CONCEPTS: &[(&str, u16, &[&str])] = &[
("osm_node", class_ids::OSM_NODE, &["Node", "OldNode"]),
// The one DERIVED concept: OSM has no junction element, so there is no
// Rails class to ground against. The bake synthesizes it from the
// routable-way graph. Its rails list is deliberately empty — see
// `tests::every_concept_sits_in_the_geo_domain`, which allows exactly
// this concept to have none rather than accepting a fabricated source.
("osm_street_node", class_ids::OSM_STREET_NODE, &[]),
("osm_way", class_ids::OSM_WAY, &["Way", "OldWay"]),
(
"osm_relation",
Expand Down Expand Up @@ -556,15 +577,29 @@ mod tests {
GEO_DOMAIN,
"{name} ({id:#06x}) is outside the Geo domain"
);
assert!(!rails.is_empty(), "{name} has no Rails source class");
// Empty rails is allowed for EXACTLY the concepts OSM does not
// have an element for — a derived concept has nothing to ground
// against, and a fabricated source class would be worse than an
// honest absence. Anything else with no rails is a mistake.
if !SYNTHESIZED.contains(name) {
assert!(!rails.is_empty(), "{name} has no Rails source class");
}
}
// Every synthesized name must actually BE a concept — otherwise the
// exemption above silently covers a typo and re-opens the hole.
for s in SYNTHESIZED {
assert!(
OSM_CONCEPTS.iter().any(|(n, _, _)| n == s),
"{s} is exempted but is not a concept"
);
}
// Ten concepts, and the ids are distinct — a copy-paste that reused
// Eleven concepts, and the ids are distinct — a copy-paste that reused
// one constant twice would pass a domain check but fail here.
assert_eq!(OSM_CONCEPTS.len(), 10);
assert_eq!(OSM_CONCEPTS.len(), 11);
let mut ids: Vec<u16> = OSM_CONCEPTS.iter().map(|(_, id, _)| *id).collect();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), 10, "two concepts share an id");
assert_eq!(ids.len(), 11, "two concepts share an id");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/ogar-vocab/src/capability_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,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(), 90);
assert_eq!(crate::class_ids::ALL.len(), 91);
for (_, id) in crate::class_ids::ALL {
assert_ne!(*id >> 8, 0x17, "a 0x17XX row reached the codebook");
}
Expand Down
45 changes: 44 additions & 1 deletion crates/ogar-vocab/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1381,6 +1381,14 @@ const CODEBOOK: &[(&str, u16)] = &[
("osm_note", 0x0F08),
("osm_gpx_trace", 0x0F09),
("osm_user", 0x0F0A),
// SYNTHESIZED — the one Geo concept with no Rails source. OSM has no
// "junction" element: a street node is DERIVED by the bake from the
// routable-way graph (Berlin 619,470 junction nodes → 233,053 rows;
// Brandenburg 590,437). It earns its own id because its VALUE SLOTS mean
// something different from an `osm_node`'s — per-edge names, adjacency,
// bearing, and a turn matrix, resolved by ClassView — and there is
// otherwise no byte on disk distinguishing the two readings.
("osm_street_node", 0x0F0B),
];

/// Codebook **domain** — the high byte of a canonical id (see
Expand Down Expand Up @@ -1985,6 +1993,20 @@ pub mod class_ids {
pub const OSM_GPX_TRACE: u16 = 0x0F09;
/// `osm_user` (`0x0F0A`) — a mapper account. Rails `User`.
pub const OSM_USER: u16 = 0x0F0A;
/// `osm_street_node` (`0x0F0B`) — a junction in the routable-way graph.
///
/// **No Rails source, by nature**: OSM has no junction element. This is
/// DERIVED by the bake wherever routable ways meet, and it exists as its
/// own concept because its value slots carry a different schema from an
/// ordinary `osm_node` — per-edge street names, adjacency, bearing and a
/// turn matrix, all resolved through its ClassView.
///
/// Without a distinct classid there is no byte on disk separating "a
/// junction row using its edge slots" from "a tagged node using its first
/// tag slot" — a reader that guessed measured **1,084,213 false hits** on
/// real Berlin data (see `osm-soa-bake`'s `street.rs`). The id IS the
/// disambiguator.
pub const OSM_STREET_NODE: u16 = 0x0F0B;

// ── 0x07XX — OSINT domain: no concept constants (low byte = APPID,
// domain-wise; q2 = 0x01 → `0x0701` is OSINT-for-q2, not a concept —
Expand Down Expand Up @@ -2096,6 +2118,7 @@ pub mod class_ids {
("osm_note", OSM_NOTE),
("osm_gpx_trace", OSM_GPX_TRACE),
("osm_user", OSM_USER),
("osm_street_node", OSM_STREET_NODE),
];

#[cfg(test)]
Expand Down Expand Up @@ -2159,7 +2182,7 @@ pub mod class_ids {
// lance-graph mirror is rebuilt against it.
assert_eq!(
ALL.len(),
90,
91,
"class_ids::ALL count changed — update this pin AND the \
lance-graph mirror COUNT_FUSE (crates/lance-graph-ogar/src/lib.rs) \
in the same PR",
Expand Down Expand Up @@ -3045,6 +3068,7 @@ pub fn all_promoted_classes() -> Vec<Class> {
osm_note(),
osm_gpx_trace(),
osm_user(),
osm_street_node(),
]
}

Expand Down Expand Up @@ -4761,6 +4785,25 @@ pub fn osm_user() -> Class {
c
}

/// The `osm_street_node` (`0x0F0B`) — a junction in the routable-way graph.
///
/// The one Geo class with **no Rails source**: OSM has no junction element, so
/// `Class::new` names the derived concept rather than a mirrored table. It is
/// synthesized by the bake wherever routable ways meet, and it exists as its
/// own class because its value slots carry a schema an `osm_node`'s do not —
/// per-edge street names, adjacency, bearing and a turn matrix.
///
/// Its association is to `Way`, not to `Node`: a junction is defined BY the
/// ways that meet at it, which is also how the bake derives it.
#[must_use]
pub fn osm_street_node() -> Class {
let mut c = Class::new("StreetNode");
c.language = Language::Unknown;
c.canonical_concept = Some("osm_street_node".to_string());
c.associations = vec![family_edge("ways", "Way")];
c
}

// ── 0x0CXX — Automation domain builders (HIRO IT-automation stack) ──
// The MARS structural CMDB (A→R→S→M `dependsOn` backbone) + the Automation
// DO-arm actuators. Shapes grounded in the vendored OGIT TTL attributes
Expand Down
Loading