Skip to content

Commit 58a64d4

Browse files
committed
ogar-vocab: mint network_layer (0x0804); render_osm review fixes (#152)
Reconcile the OCR 0x08XX domain to what the lance-graph contract mirror already declares, and address the Bugbot/codex review on #152. network_layer (0x0804) mint — the OGAR-authoritative counterpart of the concept the lance-graph mirror wire-declared first (`class_ids::ALL` 68→…→79 with the 10 OSM/Geo concepts). The KIND "a Tesseract recognizer network layer"; the concrete subclass is the classid custom-low half (a `NetworkType` ordinal), one slot not 27 — the layer graph sinks onto FacetCascade tenants. Added to all four codebook places (CODEBOOK, class_ids::{NETWORK_LAYER, ALL}, all_promoted_classes, constructor) + ogar-class-view all_canonical_classes; OCR domain-set + ALL.len() pins 3→4 / 78→79. 96 ogar-vocab + 12 ogar-class-view tests green. render_osm review fixes: - DO-arm no longer drops colliding rail tiles. The (part_of:is_a) tile is the CANONICAL castable address by design, so multiple source controllers (`Api::NodesController#show` + `NodesController#show` → `node:show`) are ONE action — but provenance was silently lost. Now every source is accumulated and cited in the doc comment (Bugbot High / codex P2). - extract_action_rail resolves the repo ROOT (not the app/models fallback dir) so controllers resolve for a models-only `src` (Bugbot Medium). - manifest writes warn instead of silently skipping when the out-dir isn't ≥5 levels under a harvest tree (Bugbot Medium). Deferred (noted on the PR): vocab/ogar.ttl + ogar.surql are stale across OCR/HR/Genetics/Geo (regenerate wholesale, not osm-only patch, codex P1); moving render_osm's private ground() into a shared lift so harvest_osm + library callers get grounded Geo IR (codex P2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
1 parent f508562 commit 58a64d4

3 files changed

Lines changed: 90 additions & 14 deletions

File tree

crates/ogar-class-view/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ use ogar_vocab::{
9797
mars_resource,
9898
mars_software,
9999
medication,
100+
network_layer,
100101
osm_changeset,
101102
osm_element_tag,
102103
osm_gpx_trace,
@@ -198,6 +199,7 @@ fn all_canonical_classes() -> Vec<(&'static str, Class)> {
198199
("unicharset", unicharset()),
199200
("recoder", recoder()),
200201
("charset", charset()),
202+
("network_layer", network_layer()),
201203
// ── 0x09XX — health (OGIT Healthcare) ──
202204
("patient", patient()),
203205
("diagnosis", diagnosis()),

crates/ogar-render-askama/examples/render_osm.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,27 @@ fn main() {
184184
let hp = root.join("harvest");
185185
fs::create_dir_all(&hp).ok();
186186
fs::write(hp.join("osm_graph.spo"), &spo).unwrap();
187+
} else {
188+
eprintln!(
189+
"warning: out-dir {} is not ≥5 levels under a harvest tree — \
190+
skipping osm_graph.spo (codex #152: no longer silent)",
191+
out.display()
192+
);
187193
}
188194

189195
// ── Action rail: (part_of : is_a) — the castable V3 action shape ──
190-
let rail = ogar_from_rails::extract_action_rail(src_path, "osm");
196+
// `extract_action_rail` joins `app/controllers` internally, so it needs the
197+
// REPO ROOT — not the `app/models` dir the struct-extraction fallback may use.
198+
// Resolve the root so controllers resolve even when `src` was the models dir
199+
// (Bugbot/codex #152: the rail was silently empty for a models-only `src`).
200+
let rail_root: &Path = if src_path.join("app/controllers").is_dir() {
201+
src_path
202+
} else if src_path.ends_with("app/models") {
203+
src_path.parent().and_then(|p| p.parent()).unwrap_or(src_path)
204+
} else {
205+
src_path
206+
};
207+
let rail = ogar_from_rails::extract_action_rail(rail_root, "osm");
191208
let mut rt = String::from(
192209
"# OSM action rail — (part_of : is_a), the V3 castable action shape\n\
193210
# a u8:u8 HHTL tile per action; cast = fix one axis, walk the other\n\
@@ -222,18 +239,33 @@ fn main() {
222239
rt.push('\n');
223240
if let Some(root) = out.ancestors().nth(4) {
224241
fs::write(root.join("harvest/osm_actions.rail"), &rt).unwrap();
242+
} else {
243+
eprintln!(
244+
"warning: out-dir {} is not ≥5 levels under a harvest tree — \
245+
skipping osm_actions.rail (codex #152: no longer silent)",
246+
out.display()
247+
);
225248
}
226249

227250
// ── DO-arm module tree: osm::<part_of>::<is_a>(input) → generated/actions.rs.
228251
// part_of = container module, is_a = action fn — standalone free functions,
229252
// never methods on the data struct. e.g. `osm::map::render(input)`. ──
230-
let mut tree: std::collections::BTreeMap<String, std::collections::BTreeMap<String, (String, String)>> =
231-
std::collections::BTreeMap::new();
253+
// The (part_of : is_a) tile is the CANONICAL rail address, so multiple
254+
// source controllers that map to the same tile (e.g. `Api::NodesController#show`
255+
// and `NodesController#show` → `node:show`) are ONE castable action by design
256+
// — the api-vs-web surface is the classid's custom-low half, not a new tile.
257+
// Accumulate ALL sources per tile so provenance is never dropped from the docs
258+
// (Bugbot/codex #152 review: the earlier `or_insert` silently lost the 2nd+).
259+
let mut tree: std::collections::BTreeMap<
260+
String,
261+
std::collections::BTreeMap<String, Vec<(String, String)>>,
262+
> = std::collections::BTreeMap::new();
232263
for r in &rail {
233264
tree.entry(r.part_of.clone())
234265
.or_default()
235266
.entry(r.is_a.clone())
236-
.or_insert((r.controller.clone(), r.action.clone()));
267+
.or_default()
268+
.push((r.controller.clone(), r.action.clone()));
237269
}
238270
let mut acts = String::from(
239271
"//! @generated DO-arm — OSM controller actions as `osm::<part_of>::<is_a>(input)`.\n\
@@ -253,15 +285,29 @@ fn main() {
253285
rustify(part_of)
254286
));
255287
let mut seen = std::collections::HashSet::new();
256-
for (is_a, (controller, action)) in isas {
288+
for (is_a, sources) in isas {
257289
let fname = rustify(is_a);
258290
if !seen.insert(fname.clone()) {
259291
continue;
260292
}
293+
// All source controllers that collapse onto this canonical tile,
294+
// deduped + sorted, cited so no route is lost from the docs.
295+
let mut srcs: Vec<String> = sources
296+
.iter()
297+
.map(|(c, a)| format!("{c}#{a}"))
298+
.collect();
299+
srcs.sort();
300+
srcs.dedup();
301+
let primary = &srcs[0];
302+
let source_line = if srcs.len() == 1 {
303+
format!("Source: `{primary}`.")
304+
} else {
305+
format!("Sources (canonical tile): `{}`.", srcs.join("`, `"))
306+
};
261307
acts.push_str(&format!(
262-
" /// `{part_of}:{is_a}` — DO arm. Source: `{controller}#{action}`.\n \
308+
" /// `{part_of}:{is_a}` — DO arm. {source_line}\n \
263309
pub fn {fname}(input: Input) -> Output {{\n \
264-
let _ = input;\n todo!(\"port {controller}#{action}\")\n }}\n"
310+
let _ = input;\n todo!(\"port {primary}\")\n }}\n"
265311
));
266312
fn_count += 1;
267313
}

crates/ogar-vocab/src/lib.rs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,7 @@ const CODEBOOK: &[(&str, u16)] = &[
11461146
("unicharset", 0x0801),
11471147
("recoder", 0x0802),
11481148
("charset", 0x0803),
1149+
("network_layer", 0x0804),
11491150
// ── 0x09XX — Health domain (clinical / patient / care) ──
11501151
// medcare-rs Healthcare-namespace promotion (Northstar T9). The 7
11511152
// entities the OGIT `NTO/Healthcare/entities/` TTL ships, projected
@@ -1614,6 +1615,13 @@ pub mod class_ids {
16141615
/// document or model asserts (distinct from the trained `unicharset`
16151616
/// inventory).
16161617
pub const CHARSET: u16 = 0x0803;
1618+
/// `network_layer` (`0x0804`) — the KIND "a Tesseract recognizer network
1619+
/// layer" (Series / LSTM / Convolve / …). ONE container slot: the specific
1620+
/// subclass is the classid's custom-low half (the `NetworkType` ordinal),
1621+
/// not 27 slots — the layer graph sinks onto `FacetCascade` tenants (the
1622+
/// ruff→OGAR network harvest lands here). Wire-declared first in the
1623+
/// lance-graph contract mirror; this is the authoritative OGAR counterpart.
1624+
pub const NETWORK_LAYER: u16 = 0x0804;
16171625

16181626
// ── 0x09XX — health domain (medcare-rs Healthcare namespace) ──
16191627

@@ -1813,6 +1821,7 @@ pub mod class_ids {
18131821
("unicharset", UNICHARSET),
18141822
("recoder", RECODER),
18151823
("charset", CHARSET),
1824+
("network_layer", NETWORK_LAYER),
18161825
// 0x09XX — health
18171826
("patient", PATIENT),
18181827
("diagnosis", DIAGNOSIS),
@@ -1921,7 +1930,7 @@ pub mod class_ids {
19211930
// lance-graph mirror is rebuilt against it.
19221931
assert_eq!(
19231932
ALL.len(),
1924-
78,
1933+
79,
19251934
"class_ids::ALL count changed — update this pin AND the \
19261935
lance-graph mirror COUNT_FUSE (crates/lance-graph-ogar/src/lib.rs) \
19271936
in the same PR",
@@ -2743,10 +2752,11 @@ pub fn all_promoted_classes() -> Vec<Class> {
27432752
// `osint_system` / `osint_person` mints); no calls follow — OGAR
27442753
// vocabulary carries no OSINT concept names, see the CODEBOOK
27452754
// 0x07XX section note.
2746-
// 0x08XX — OCR arm (3 container kinds), in class_ids::ALL order.
2755+
// 0x08XX — OCR arm (4 container kinds), in class_ids::ALL order.
27472756
unicharset(),
27482757
recoder(),
27492758
charset(),
2759+
network_layer(),
27502760
// 0x09XX — health arm (7 OGIT Healthcare concepts), in
27512761
// class_ids::ALL order.
27522762
patient(),
@@ -3745,6 +3755,23 @@ pub fn charset() -> Class {
37453755
c
37463756
}
37473757

3758+
/// NetworkLayer (`0x0804`) — the KIND "a Tesseract recognizer network layer"
3759+
/// (Series / LSTM / Convolve / …). ONE container slot: the concrete subclass is
3760+
/// the classid's custom-low half (the `NetworkType` ordinal), never a per-type
3761+
/// slot — the layer graph sinks onto `FacetCascade` value tenants (the ruff→OGAR
3762+
/// network harvest lands here). Minimal AR shape; a `network_type` ordinal
3763+
/// attribute names the subclass.
3764+
#[must_use]
3765+
pub fn network_layer() -> Class {
3766+
let mut c = Class::new("NetworkLayer");
3767+
c.language = Language::Unknown;
3768+
c.canonical_concept = Some("network_layer".to_string());
3769+
let mut network_type = Attribute::new("network_type");
3770+
network_type.type_name = Some("u8".to_string());
3771+
c.attributes = vec![network_type];
3772+
c
3773+
}
3774+
37483775
// ─────────────────────────────────────────────────────────────────────
37493776
// 0x09XX — Health domain (OGIT Healthcare). The reusable Active-Record
37503777
// shape for the clinical concepts. `diagnosis` (0x0902) is the worked
@@ -5178,16 +5205,17 @@ mod tests {
51785205
}
51795206
// Counts line up with the codebook blocks.
51805207
assert_eq!(concepts_in_domain(ConceptDomain::Health).count(), 7);
5181-
// 0x08XX OCR: the three container KINDS (unicharset/recoder/charset).
5182-
// Content (the 112 unichars, code tables) never becomes concepts —
5183-
// see the CODEBOOK 0x08XX section note (Osint zero-rows precedent).
5184-
assert_eq!(concepts_in_domain(ConceptDomain::Ocr).count(), 3);
5208+
// 0x08XX OCR: the four container KINDS (unicharset/recoder/charset/
5209+
// network_layer). Content (the 112 unichars, code tables) never becomes
5210+
// concepts — see the CODEBOOK 0x08XX section note (Osint zero-rows
5211+
// precedent).
5212+
assert_eq!(concepts_in_domain(ConceptDomain::Ocr).count(), 4);
51855213
let ocr: Vec<&str> = concepts_in_domain(ConceptDomain::Ocr)
51865214
.map(|(name, _)| name)
51875215
.collect();
51885216
assert_eq!(
51895217
ocr,
5190-
["unicharset", "recoder", "charset"],
5218+
["unicharset", "recoder", "charset", "network_layer"],
51915219
"OCR domain set drift — re-sync the consumer coverage gate",
51925220
);
51935221
assert_eq!(concepts_in_domain(ConceptDomain::HR).count(), 4);

0 commit comments

Comments
 (0)