diff --git a/cockpit/src/BodyV3.tsx b/cockpit/src/BodyV3.tsx index cde66ac04..435c4f9ef 100644 --- a/cockpit/src/BodyV3.tsx +++ b/cockpit/src/BodyV3.tsx @@ -21,7 +21,11 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; const PAGE_BG = 0x0a0e17; -const FMA_V3_CLASSID = 0x10000a01; +// Post-flip form (canon 0x0A01 HIGH, V3 marker 0x1000 LOW — 2026-07-02). +const FMA_V3_CLASSID = 0x0a011000; +// Pre-flip stored form — the body.soa GitHub-Release asset +// (fma-body-soa-v3-v1) still carries it until re-baked + re-released. +const FMA_V3_CLASSID_LEGACY = 0x10000a01; const LAYERS: { id: number; name: string; color: string }[] = [ { id: 1, name: 'skin', color: '#dba88a' }, @@ -433,7 +437,7 @@ export function BodyV3() { {d && (
{d.nConcepts.toLocaleString()} concepts ·{' '} - {d.classid === FMA_V3_CLASSID + {(d.classid === FMA_V3_CLASSID || d.classid === FMA_V3_CLASSID_LEGACY) ? classid 0x{d.classid.toString(16)} ✓ V3 (part_of:is_a) : classid 0x{d.classid.toString(16)}}
diff --git a/cpic/docs/INGEST.md b/cpic/docs/INGEST.md index 321ab1948..5ba05c9cb 100644 --- a/cpic/docs/INGEST.md +++ b/cpic/docs/INGEST.md @@ -40,19 +40,32 @@ Both hierarchies are *already in the CPIC columns* — nothing is invented: partonomy is the `part_of` axis, and ATC drug codes are a second ready-made `is_a` cascade. -## classid — pharmacogenomics domain `0x0C` - -`classid` prefix-routes the entity kind (the OGAR `ClassView` dispatch). We use domain -`0x0C` (anatomy was `0x0A`): +## classid — pharmacogenomics, Genetics domain `0x0E01` (re-minted 2026-07-02) + +> **Note (2026-07-02):** `SAMPLE_GUIDS.tsv` was regenerated from a fresh +> `cargo run --release --bin ingest -- data out 4000` run against this +> re-mint — every row's tail (HEEL/HIP/TWIG/family/identity) is byte-identical +> to the pre-flip sample; only the classid prefix changed. + +`classid` prefix-routes the entity kind (the OGAR `ClassView` dispatch). The +original local scheme used domain `0x0C`, which collides with the Automation +domain (`lance-graph-contract::ogar_codebook::ConceptDomain::Automation`) and +predates the operator's Genetics ruling. Re-minted as INTERIM canon-high +Genetics:q2 ids, matching the `lance-graph-contract` classid half-order flip +(canon HIGH / custom LOW): canon `0x0E01` = Genetics domain (`0x0E`) + appid +`0x01` (q2); the LOW half is this crate's local kind slot. This crate is +dep-free and does not pull the real `lance-graph-contract` ClassView +catalogue — the full contract-pull re-mint that dissolves this local scheme +entirely is the tracked follow-up. | classid | entity | part_of (high) | is_a (low) | |---|---|---|---| -| `0x000C0001` | gene | `pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19` | `entity / gene` | -| `0x000C0002` | allele (`*2`) | gene partonomy ++ allele | `allele / no_function` | -| `0x000C0003` | diplotype (`*2/*2`) | gene ++ `diplotypes` ++ dip | `diplotype / homozygous` | -| `0x000C0004` | phenotype | gene ++ `phenotypes` ++ result | `phenotype / poor_metabolizer` | -| `0x000C0005` | drug | `pharmacogenome / drugs / name` | `drug / N / N06 / N06A / N06AA` | -| `0x000C0006` | recommendation | `recommendations / g{id} / drug` | `recommendation / strong` | +| `0x0E010001` | gene | `pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19` | `entity / gene` | +| `0x0E010002` | allele (`*2`) | gene partonomy ++ allele | `allele / no_function` | +| `0x0E010003` | diplotype (`*2/*2`) | gene ++ `diplotypes` ++ dip | `diplotype / homozygous` | +| `0x0E010004` | phenotype | gene ++ `phenotypes` ++ result | `phenotype / poor_metabolizer` | +| `0x0E010005` | drug | `pharmacogenome / drugs / name` | `drug / N / N06 / N06A / N06AA` | +| `0x0E010006` | recommendation | `recommendations / g{id} / drug` | `recommendation / strong` | - **family (u24)** = the basin: the gene symbol (gene/allele/diplotype/phenotype), the ATC root letter (drug), or `rec:g{id}` (recommendation). Groups all of a gene's @@ -66,10 +79,10 @@ cascade. ```text allele guid HEEL HIP TWIG family -CYP2C19 *2 000c0002-c358-ec6f-f76f-d69bfe558274 c358 ec6f f76f d69bfe (No function) -CYP2C9 *2 000c0002-c358-ecd8-f7d8-fbd6cbd5c622 c358 ecd8 f7d8 fbd6cb (Decreased function) -CYP2C19 *1 000c0002-c358-ec07-f707-d69bfe3c1176 c358 ec07 f707 d69bfe (Normal function) -CYP2D6 *4 000c0002-c358-ec6f-f76f-1066f96f7ace c358 ec6f f76f 1066f9 (No function) +CYP2C19 *2 0e010002-c358-ec6f-f76f-d69bfe558274 c358 ec6f f76f d69bfe (No function) +CYP2C9 *2 0e010002-c358-ecd8-f7d8-fbd6cbd5c622 c358 ecd8 f7d8 fbd6cb (Decreased function) +CYP2C19 *1 0e010002-c358-ec07-f707-d69bfe3c1176 c358 ec07 f707 d69bfe (Normal function) +CYP2D6 *4 0e010002-c358-ec6f-f76f-1066f96f7ace c358 ec6f f76f 1066f9 (No function) ``` - **part_of HIGH bytes** `c3 / ec / f7` = `pharmacogenome → CYP → CYP2`, shared by all diff --git a/cpic/docs/SAMPLE_GUIDS.tsv b/cpic/docs/SAMPLE_GUIDS.tsv index b43c82c52..d2954cda8 100644 --- a/cpic/docs/SAMPLE_GUIDS.tsv +++ b/cpic/docs/SAMPLE_GUIDS.tsv @@ -1,26 +1,26 @@ guid kind label part_of is_a -000c0001-c3fa-ec2a-f72a-fbd6cb3779b9 gene CYP2C9 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C9 entity / gene -000c0001-c3fa-ec2a-f72a-1066f93779b9 gene CYP2D6 pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 entity / gene -000c0001-c3fa-1b2a-982a-61ef4f3779b9 gene HLA-B pharmacogenome / HLA / HLA-B entity / gene -000c0001-c3fa-ec2a-f72a-d69bfe3779b9 gene CYP2C19 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 entity / gene -000c0001-c3fa-f92a-8f2a-ec80083779b9 gene TPMT pharmacogenome / TPMT / TPMT entity / gene -000c0002-c358-ec6f-f76f-d69bfe4cda56 allele CYP2C19 *4 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 / 4 allele / no_function -000c0002-c358-ec6f-f76f-1066f96f7ace allele CYP2D6 *4 pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / 4 allele / no_function -000c0002-c358-1bd5-98d5-61ef4f6ef372 allele HLA-B *57:01 pharmacogenome / HLA / HLA-B / 57_01 allele / uncertain_function -000c0002-c358-ec6f-f76f-d69bfe558274 allele CYP2C19 *2 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 / 2 allele / no_function -000c0002-c358-ecd8-f7d8-fbd6cbd5c622 allele CYP2C9 *2 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C9 / 2 allele / decreased_function -000c0002-c358-ec07-f707-d69bfe3c1176 allele CYP2C19 *1 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 / 1 allele / normal_function -000c0002-c358-ec6f-f76f-fbd6cb6b6a6d allele CYP2C9 *3 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C9 / 3 allele / no_function -000c0002-c358-ec07-f707-1066f9fd85b3 allele CYP2D6 *1 pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / 1 allele / normal_function -000c0004-c347-ecb1-f7b1-1066f93779b9 phenotype CYP2D6 Ultrarapid Metabolizer pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / phenotypes / ultrarapid_metabolizer phenotype / ultrarapid_metabolizer -000c0004-c347-ecb1-f7b1-1066f96ef372 phenotype CYP2D6 Ultrarapid Metabolizer pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / phenotypes / ultrarapid_metabolizer phenotype / ultrarapid_metabolizer -000c0004-c347-ec7a-f77a-1066f9a66d2b phenotype CYP2D6 Normal Metabolizer pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / phenotypes / normal_metabolizer phenotype / normal_metabolizer -000c0004-c347-f9a8-8fa8-ec80083779b9 phenotype TPMT Intermediate Metabolizer pharmacogenome / TPMT / TPMT / phenotypes / intermediate_metabolizer phenotype / intermediate_metabolizer -000c0004-c347-f9e9-8fe9-ec80086ef372 phenotype TPMT Poor Metabolizer pharmacogenome / TPMT / TPMT / phenotypes / poor_metabolizer phenotype / poor_metabolizer -000c0004-c347-f9db-8fdb-ec8008a66d2b phenotype TPMT Indeterminate pharmacogenome / TPMT / TPMT / phenotypes / indeterminate phenotype / indeterminate -000c0005-c3af-cb3e-233c-1c839f6ef372 drug abacavir pharmacogenome / drugs / abacavir drug / J / J05 / J05A / J05AF / abacavir -000c0005-c3af-cb0a-630d-1c7cd36ef372 drug amitriptyline pharmacogenome / drugs / amitriptyline drug / N / N06 / N06A / N06AA / amitriptyline -000c0005-c3af-cba6-3010-1c9137a66d2b drug warfarin pharmacogenome / drugs / warfarin drug / B / B01 / B01A / B01AA / warfarin -000c0005-c3af-cba6-b610-1c9137bbcdc8 drug clopidogrel pharmacogenome / drugs / clopidogrel drug / B / B01 / B01A / B01AC / clopidogrel -000c0006-c7ee-454c-db4c-a2917a3779b9 recommendation g100421 RxNorm:190521 [HLA-B:*57:01 negative] -> Strong recommendations / g100421 / rxnorm_190521 recommendation / strong -000c0006-c7ee-454c-db4c-a2917a6ef372 recommendation g100421 RxNorm:190521 [HLA-B:*57:01 positive] -> Strong recommendations / g100421 / rxnorm_190521 recommendation / strong +0e010001-c3fa-ec2a-f72a-fbd6cb3779b9 gene CYP2C9 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C9 entity / gene +0e010001-c3fa-ec2a-f72a-1066f93779b9 gene CYP2D6 pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 entity / gene +0e010001-c3fa-1b2a-982a-61ef4f3779b9 gene HLA-B pharmacogenome / HLA / HLA-B entity / gene +0e010001-c3fa-ec2a-f72a-d69bfe3779b9 gene CYP2C19 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 entity / gene +0e010001-c3fa-f92a-8f2a-ec80083779b9 gene TPMT pharmacogenome / TPMT / TPMT entity / gene +0e010002-c358-ec6f-f76f-d69bfe4cda56 allele CYP2C19 *4 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 / 4 allele / no_function +0e010002-c358-ec6f-f76f-1066f96f7ace allele CYP2D6 *4 pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / 4 allele / no_function +0e010002-c358-1bd5-98d5-61ef4f6ef372 allele HLA-B *57:01 pharmacogenome / HLA / HLA-B / 57_01 allele / uncertain_function +0e010002-c358-ec6f-f76f-d69bfe558274 allele CYP2C19 *2 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 / 2 allele / no_function +0e010002-c358-ecd8-f7d8-fbd6cbd5c622 allele CYP2C9 *2 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C9 / 2 allele / decreased_function +0e010002-c358-ec07-f707-d69bfe3c1176 allele CYP2C19 *1 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C19 / 1 allele / normal_function +0e010002-c358-ec6f-f76f-fbd6cb6b6a6d allele CYP2C9 *3 pharmacogenome / CYP / CYP2 / CYP2C / CYP2C9 / 3 allele / no_function +0e010002-c358-ec07-f707-1066f9fd85b3 allele CYP2D6 *1 pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / 1 allele / normal_function +0e010004-c347-ecb1-f7b1-1066f93779b9 phenotype CYP2D6 Ultrarapid Metabolizer pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / phenotypes / ultrarapid_metabolizer phenotype / ultrarapid_metabolizer +0e010004-c347-ecb1-f7b1-1066f96ef372 phenotype CYP2D6 Ultrarapid Metabolizer pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / phenotypes / ultrarapid_metabolizer phenotype / ultrarapid_metabolizer +0e010004-c347-ec7a-f77a-1066f9a66d2b phenotype CYP2D6 Normal Metabolizer pharmacogenome / CYP / CYP2 / CYP2D / CYP2D6 / phenotypes / normal_metabolizer phenotype / normal_metabolizer +0e010004-c347-f9a8-8fa8-ec80083779b9 phenotype TPMT Intermediate Metabolizer pharmacogenome / TPMT / TPMT / phenotypes / intermediate_metabolizer phenotype / intermediate_metabolizer +0e010004-c347-f9e9-8fe9-ec80086ef372 phenotype TPMT Poor Metabolizer pharmacogenome / TPMT / TPMT / phenotypes / poor_metabolizer phenotype / poor_metabolizer +0e010004-c347-f9db-8fdb-ec8008a66d2b phenotype TPMT Indeterminate pharmacogenome / TPMT / TPMT / phenotypes / indeterminate phenotype / indeterminate +0e010005-c3af-cb3e-233c-1c839f6ef372 drug abacavir pharmacogenome / drugs / abacavir drug / J / J05 / J05A / J05AF / abacavir +0e010005-c3af-cb0a-630d-1c7cd36ef372 drug amitriptyline pharmacogenome / drugs / amitriptyline drug / N / N06 / N06A / N06AA / amitriptyline +0e010005-c3af-cba6-3010-1c9137a66d2b drug warfarin pharmacogenome / drugs / warfarin drug / B / B01 / B01A / B01AA / warfarin +0e010005-c3af-cba6-b610-1c9137bbcdc8 drug clopidogrel pharmacogenome / drugs / clopidogrel drug / B / B01 / B01A / B01AC / clopidogrel +0e010006-c7ee-454c-db4c-a2917a3779b9 recommendation g100421 RxNorm:190521 [HLA-B:*57:01 negative] -> Strong recommendations / g100421 / rxnorm_190521 recommendation / strong +0e010006-c7ee-454c-db4c-a2917a6ef372 recommendation g100421 RxNorm:190521 [HLA-B:*57:01 positive] -> Strong recommendations / g100421 / rxnorm_190521 recommendation / strong diff --git a/cpic/src/lib.rs b/cpic/src/lib.rs index ee9bad060..24a6deb13 100644 --- a/cpic/src/lib.rs +++ b/cpic/src/lib.rs @@ -6,13 +6,23 @@ //! //! POC over published CPIC rules — NOT clinical decision support. -// ── classid: pharmacogenomics domain 0x0C (cf. anatomy 0x0A used by fma/converge) ── -pub const CID_GENE: u32 = 0x000C_0001; -pub const CID_ALLELE: u32 = 0x000C_0002; -pub const CID_DIPLOTYPE: u32 = 0x000C_0003; -pub const CID_PHENOTYPE: u32 = 0x000C_0004; -pub const CID_DRUG: u32 = 0x000C_0005; -pub const CID_REC: u32 = 0x000C_0006; +// ── classid: pharmacogenomics — re-minted 2026-07-02. The original local scheme +// (`0x000C_000N`) predated the operator's Genetics ruling and collided with the +// Automation domain (0x0C is Automation, not Genetics — see +// `lance-graph-contract::ogar_codebook::ConceptDomain::Automation`). Re-minted as +// INTERIM canon-high Genetics:q2 ids, matching the `lance-graph-contract` classid +// half-order flip (canon in the HIGH half, custom in the LOW half): canon `0x0E01` +// = Genetics domain (`0x0E`) + appid `0x01` (q2); the LOW half is this crate's +// local kind slot (GENE=1, ALLELE=2, DIPLOTYPE=3, PHENOTYPE=4, DRUG=5, REC=6). +// This is dep-free and does NOT pull the real `lance-graph-contract` ClassView +// catalogue — the full contract-pull re-mint that dissolves this local scheme +// entirely is the tracked follow-up (see `docs/INGEST.md`). +pub const CID_GENE: u32 = 0x0E01_0001; +pub const CID_ALLELE: u32 = 0x0E01_0002; +pub const CID_DIPLOTYPE: u32 = 0x0E01_0003; +pub const CID_PHENOTYPE: u32 = 0x0E01_0004; +pub const CID_DRUG: u32 = 0x0E01_0005; +pub const CID_REC: u32 = 0x0E01_0006; // ── FNV-1a (the same prefix-cascade generator the fma converge bin uses) ── const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325; diff --git a/crates/aiwar-ingest/tests/fixtures/codebook_normalize.py b/crates/aiwar-ingest/tests/fixtures/codebook_normalize.py index d3a1ad690..6eec49b2e 100644 --- a/crates/aiwar-ingest/tests/fixtures/codebook_normalize.py +++ b/crates/aiwar-ingest/tests/fixtures/codebook_normalize.py @@ -3,7 +3,10 @@ into ONE codebook-based, non-serialized Gotham/neo4j test fixture. CANON model (lance-graph contract::aiwar + E-FAMILY-ADAPTER + OGAR codebook): - - classid OSINT = 0x0700 (NodeGuid::CLASSID_OSINT, >>8 == 0x07) + - classid OSINT = 0x0700 (NodeGuid::CLASSID_OSINT, >>8 == 0x07) — this is the + bare u16 canon id, the HIGH half of the composed u32 classid since the + 2026-07-02 half-order flip (NodeGuid::CLASSID_OSINT == 0x0700_0000); this + fixture never composes the full u32, so no functional change from the flip. - a node is its HEAD only: classid | family(mixin) | identity(u16) | edge-adapters - mixin: an entity inherits its category by REFERENCE (family-node id), never a copy - identity = 4 nibbles (u16) @@ -13,6 +16,9 @@ import json, re, sys, glob, os ROOT = os.environ.get("AIWAR_HARVEST", "/home/user/aiwar-neo4j-harvest") +# u16 canon id — the HIGH half of the composed u32 classid since the +# 2026-07-02 flip (NodeGuid::CLASSID_OSINT == 0x0700_0000). This script only +# ever emits the bare u16 (see @meta below), never the composed u32. OSINT_CLASSID = 0x0700 # JSON N_ -> canonical family label (matches the cypher node labels) diff --git a/crates/cockpit-server/src/main.rs b/crates/cockpit-server/src/main.rs index 19e50ef90..b137ae6f3 100644 --- a/crates/cockpit-server/src/main.rs +++ b/crates/cockpit-server/src/main.rs @@ -217,7 +217,8 @@ async fn main() { // CPIC pharmacogenomics for the /cpic cockpit (cpic::reason over the baked CPIC tables) .route("/api/cpic/reason", post(pgx::cpic_reason_handler)) .route("/api/cpic/catalog", get(pgx::cpic_catalog_handler)) - // OSINT domain (classid 0x0700): the harvest as a CANON family-basin graph + // OSINT domain (canon classid 0x0700 — HIGH half since the 2026-07-02 + // flip): the harvest as a CANON family-basin graph // (round→anchor basins, GUID-v2 tail), displayed via the OGAR ClassView. .route("/api/graph/osint", get(osint_gotham::osint_graph_handler)) // Pre-baked enriched OSINT SoA bytes — the 3D view (/osint3d) fetches diff --git a/crates/cockpit-server/src/osint_gotham.rs b/crates/cockpit-server/src/osint_gotham.rs index 78a440b00..34f8bb80f 100644 --- a/crates/cockpit-server/src/osint_gotham.rs +++ b/crates/cockpit-server/src/osint_gotham.rs @@ -1,5 +1,9 @@ //! OSINT / Palantir-Gotham domain (classid `0x0700`) — the aiwar harvest as a //! CANON family-basin graph, rendered through the OGAR Active-Record `ClassView`. +//! `0x0700` is the canon concept id: since the 2026-07-02 half-order flip it is +//! the HIGH u16 of the composed `u32` classid (`0x0700_0000`, `NodeGuid::CLASSID_OSINT`); +//! the pre-flip stored form `0x0000_0700` still resolves via `CLASSID_OSINT_LEGACY`. +//! Bare `0x0700` below always means the canon value, not the full composed classid. //! //! The corrected model (operator-locked this session): //! @@ -500,9 +504,12 @@ pub fn osint_graph() -> &'static Arc> { OSINT_GRAPH.get_or_init(|| Arc::new(RwLock::new(GraphSnapshot::empty()))) } -/// The ClassView ClassId for the OSINT domain (low u16 of `CLASSID_OSINT`). +/// The ClassView ClassId for the OSINT domain — the concept id, i.e. the CANON +/// half of `CLASSID_OSINT` (the HIGH u16 since the 2026-07-02 half-order flip; +/// `classid & 0xFFFF` would now read the CUSTOM half and silently return the +/// wrong value). fn osint_class_id() -> u16 { - (NodeGuid::CLASSID_OSINT & 0xFFFF) as u16 + lance_graph_contract::classid_concept(NodeGuid::CLASSID_OSINT) } // ───────────────────────────────────────────────────────────────────────────── @@ -826,7 +833,13 @@ pub fn build_osint_gotham(graph: &AiWarGraph, rounds: &[EncounterRound]) -> Grap let class_label = label_of_order(order); let mut props: HashMap = src.properties.clone(); props.insert("guid".to_string(), Value::String(r.key.to_hex_v2())); - props.insert("classid".to_string(), Value::String("00000700".to_string())); + // Compose dynamically rather than hardcoding the stored form — the + // 2026-07-02 half-order flip moved canon 0x0700 into the HIGH half, + // so this now yields "07000000" (was "00000700" pre-flip). + props.insert( + "classid".to_string(), + Value::String(format!("{:08x}", NodeGuid::CLASSID_OSINT)), + ); props.insert("class_order".to_string(), Value::from(order)); props.insert("class".to_string(), Value::String(class_label.to_string())); props.insert("basin".to_string(), Value::String(format!("{basin:02x}"))); @@ -1249,9 +1262,14 @@ mod tests { let rows = osint_node_rows(&g, &plan); assert_eq!(rows.len(), g.node_count(), "one OSINT row per entity"); for (i, row) in rows.iter().enumerate() { - // classid 0x0700 (the OSINT domain byte is 0x07) + // classid 0x0700_0000 (canon 0x0700 sits in the HIGH half since + // the 2026-07-02 flip; route through the contract helper rather + // than deriving the domain byte via a raw shift). assert_eq!(row.key.classid(), NodeGuid::CLASSID_OSINT); - assert_eq!(row.key.classid() >> 8, 0x07); + assert_eq!( + lance_graph_contract::ogar_codebook::classid_canon(row.key.classid()) >> 8, + 0x07 + ); // GUID-v2 tail: identity == index, family == basin byte (high byte 0) assert_eq!(row.key.identity_v2(), i as u16); assert_eq!(row.key.family_v2() >> 8, 0, "basin is an 8-bit byte"); diff --git a/crates/osint-bake/Cargo.toml b/crates/osint-bake/Cargo.toml index 702a96f33..95b039257 100644 --- a/crates/osint-bake/Cargo.toml +++ b/crates/osint-bake/Cargo.toml @@ -15,7 +15,8 @@ path = "src/main.rs" [dependencies] aiwar-ingest = { path = "../aiwar-ingest" } # osint-bake is the ONLY crate that mints on the V3 cascade tail (the FMA bake: -# CLASSID_FMA_V3 0x1000_0A01 → ReadMode::FMA_V3, the part_of:is_a reading of the +# CLASSID_FMA_V3 0x0A01_1000 (canon 0x0A01 HIGH, V3 marker 0x1000 LOW — 2026-07-02 +# half-order flip) → ReadMode::FMA_V3, the part_of:is_a reading of the # SAME leaf·family·identity bytes). guid-v3-tail is requested here, NOT workspace- # wide, so it doesn't force the feature onto cockpit-server's Railway build. lance-graph-contract = { workspace = true, features = ["guid-v3-tail"] } diff --git a/crates/osint-bake/src/bin/body.rs b/crates/osint-bake/src/bin/body.rs index 11a92504b..4a81e1fe6 100644 --- a/crates/osint-bake/src/bin/body.rs +++ b/crates/osint-bake/src/bin/body.rs @@ -37,8 +37,10 @@ use lance_graph_contract::canonical_node::{classid_read_mode, NodeGuid}; use std::path::{Path, PathBuf}; -/// FMA V3 cascade key (`0x1000_0A01`) — same constant `bin/fma.rs` uses; the V3 -/// generation marker `0x1000` over the canon `anatomical_structure` concept `0x0A01`. +/// FMA V3 cascade key (`0x0A01_1000` since the 2026-07-02 half-order flip; pre-flip +/// stored form `0x1000_0A01`) — same constant `bin/fma.rs` uses; the canon +/// `anatomical_structure` concept `0x0A01` (HIGH half) paired with the V3 +/// generation marker `0x1000` (LOW half). const CLASSID_FMA: u32 = NodeGuid::CLASSID_FMA_V3; /// One 8:8 HHTL tier: `[container-mixin : identity]` (mirrors `fma.rs::tier`). diff --git a/crates/osint-bake/src/bin/fma.rs b/crates/osint-bake/src/bin/fma.rs index 9485cac89..ddf96e8c9 100644 --- a/crates/osint-bake/src/bin/fma.rs +++ b/crates/osint-bake/src/bin/fma.rs @@ -58,16 +58,18 @@ use std::path::{Path, PathBuf}; /// The CEILING global-category pole (HEEL=HIP=0xFFFF; sentinel through TWIG = leaf-grain). const CEILING: u16 = 0xFFFF; -/// FMA classid — the **V3 cascade-key** `CLASSID_FMA_V3` (`0x1000_0A01`): the V3 -/// generation marker `0x1000` in the HIGH (custom) u16, the canon `anatomical_structure` -/// concept `0x0A01` preserved in the LOW u16 (so `classid_concept_domain` still routes -/// OGAR's `ConceptDomain::Anatomy`, high byte `0x0A`). V3 is a *reading* of the SAME +/// FMA classid — the **V3 cascade-key** `CLASSID_FMA_V3` (`0x0A01_1000` since the +/// 2026-07-02 half-order flip): the canon `anatomical_structure` concept `0x0A01` +/// in the HIGH (canon) u16, the V3 generation marker `0x1000` preserved in the LOW +/// (custom) u16 (so `classid_concept_domain` still routes OGAR's +/// `ConceptDomain::Anatomy`, high byte `0x0A`). V3 is a *reading* of the SAME /// leaf·family·identity tail v2 mints — the `(part_of:is_a)` cascade reinterprets the /// 8:8 tiers, never re-carves — so the node still mints through `new_v2` (via /// `mint_for`'s V3 arm), resolving to `ReadMode::FMA_V3`. Migrated from the legacy V2 /// `0x0000_0A01`; the heart slice is soft-tissue anatomy (universal-root concept; /// OGAR reserves `0x0A02..0x0A04` for skeleton/bone/joint). See OGAR PR #116 /// (`docs/FMA-SKELETON-CONVERGENCE-ANCHOR.md`) and the V3 set in `canonical_node.rs`. +/// Pre-flip stored form: `0x1000_0A01` (`CLASSID_FMA_V3_LEGACY`). const CLASSID_FMA: u32 = NodeGuid::CLASSID_FMA_V3; // class bytes → cockpit colour/label (see FmaGraph.tsx). diff --git a/crates/osint-bake/src/lib.rs b/crates/osint-bake/src/lib.rs index c9a3607f1..a5f9190ea 100644 --- a/crates/osint-bake/src/lib.rs +++ b/crates/osint-bake/src/lib.rs @@ -51,8 +51,10 @@ pub mod morton; /// inside the one class (not sprinkled across classids). The function defined at /// order *i* carries label *i*; an instance inherits its label by the order it /// carries in its value tenant. This is the consumer-side home of the schema for -/// the single OSINT class `0x0700`; the AR-native home is `ogar-vocab`'s `0x0700` -/// `ObjectView` (an upstream OGAR change, out of this repo's push scope). +/// the single OSINT class `0x0700` (the canon id — the HIGH half of the composed +/// `u32` classid since the 2026-07-02 half-order flip); the AR-native home is +/// `ogar-vocab`'s `0x0700` `ObjectView` (an upstream OGAR change, out of this +/// repo's push scope). /// The complete label↔order row: every label an item can carry has a slot, so /// no item falls through to "Other" (an item with a label but no order would be /// missing its label↔identity pairing). Orders 0-4 are the entity types; 5-6 are @@ -604,7 +606,7 @@ pub fn osint_node_rows(graph: &AiWarGraph, plan: &BasinPlan) -> Vec { NodeRow { key: NodeGuid::new_v2( - NodeGuid::CLASSID_OSINT, // classid 0x0700 — the ONE OSINT class + NodeGuid::CLASSID_OSINT, // classid 0x0700_0000 — canon 0x0700 (HIGH half) — the ONE OSINT class heel, // HEEL — theme, or 0xFFFF ceiling for a dimension hip, // HIP — anchor, or 0xFFFF ceiling for a dimension twig, // TWIG — axis grain for a ceiling-pole dimension @@ -898,9 +900,14 @@ mod tests { let rows = osint_node_rows(&g, &plan); assert_eq!(rows.len(), g.node_count(), "one OSINT row per entity"); for (i, row) in rows.iter().enumerate() { - // classid 0x0700 (the OSINT domain byte is 0x07) + // classid 0x0700_0000 (canon 0x0700 sits in the HIGH half since + // the 2026-07-02 flip; route through the contract helper rather + // than deriving the domain byte via a raw shift). assert_eq!(row.key.classid(), NodeGuid::CLASSID_OSINT); - assert_eq!(row.key.classid() >> 8, 0x07); + assert_eq!( + lance_graph_contract::ogar_codebook::classid_canon(row.key.classid()) >> 8, + 0x07 + ); // GUID-v2 tail: identity == index, family == basin byte (high byte 0) assert_eq!(row.key.identity_v2(), i as u16); assert_eq!(row.key.family_v2() >> 8, 0, "basin is an 8-bit byte"); diff --git a/crates/osint-bake/tools/body-soa-wire/src/main.rs b/crates/osint-bake/tools/body-soa-wire/src/main.rs index 2ebc81956..db06a8c5e 100644 --- a/crates/osint-bake/tools/body-soa-wire/src/main.rs +++ b/crates/osint-bake/tools/body-soa-wire/src/main.rs @@ -24,7 +24,7 @@ use lance_graph_contract::canonical_node::{classid_read_mode, NodeGuid}; use ndarray::hpc::quantized::F16; use ndarray::hpc::splat3d::helix_orient; -const CLASSID_FMA: u32 = NodeGuid::CLASSID_FMA_V3; // 0x1000_0A01 +const CLASSID_FMA: u32 = NodeGuid::CLASSID_FMA_V3; // 0x0A01_1000 (canon HIGH, V3 marker LOW — 2026-07-02 flip; pre-flip 0x1000_0A01) fn tile(part_of: u8, is_a: u8) -> u16 { ((part_of as u16) << 8) | is_a as u16 } diff --git a/fma/README.md b/fma/README.md index 376b24758..f7bc7403b 100644 --- a/fma/README.md +++ b/fma/README.md @@ -21,6 +21,11 @@ BodyParts3D meshes ──tissue (is_a tree)──► triangle rasterizer (z-buff (prefix-routable cascade + golden-stride identity mint) ``` +> Order flipped 2026-07-02 — canon HIGH / custom LOW. The worked GUID examples +> below use the post-flip classid form (`0x0A01_0000` soft tissue, +> `0x0A02_0000` skeleton, printed as `0a010000…` / `0a020000…`), matching the +> `lance-graph-contract` half-order flip even though this crate is dep-free. + - **Geometry** is the real mesh triangles (not gaussian splats): per-triangle z-buffered fill with smooth per-vertex (Gouraud) normals, two-sided shading, FMA-tissue color (bone ivory, muscle red, vessel red, nerve yellow, …). @@ -40,7 +45,7 @@ BodyParts3D meshes ──tissue (is_a tree)──► triangle rasterizer (z-buff | `serve` | dep-free std HTTP server, all routes under `/FMA` (binds `0.0.0.0:$PORT`) | | `guid` | mint the part_of GUID per FMA node → `guid/guid_manifest.tsv`, `guid/fj_guid.tsv` | | `converge` | **v3**: cascading-HHTL `(place:tissue)` **canonical NodeGuid** + `connected_to` edges → `guid/{guid_converged,nodes,edges}.tsv` | -| `graph` | **v3 render**: SOLID triangle surface colored by `tissue`, with a GUID **prefix** that selects the subtree (`graph … 00000a02` = skeleton) → `graph/graph_.png` | +| `graph` | **v3 render**: SOLID triangle surface colored by `tissue`, with a GUID **prefix** that selects the subtree (`graph … 0a020000` = skeleton) → `graph/graph_.png` | | `cockpit_bake` | bake the full body → `cockpit/public/fma_body.mesh` (SPM1, opacity = layer id) for the **`/fma-body`** cockpit page (layer toggles + transparency) | | `soa_scan` | 1M-row SoA scalability PoC: key-only **prefix-route** scan vs full **value-decode** scan (~90× at 1M — the canon's *"prerender with zero value decode"*) | | `anchor` | compression study: cascade vs raw-cartesian vs Cartesian-Skeleton hybrid | @@ -95,15 +100,15 @@ Three things make `place` and the render converge (`classid`-dispatched, OGAR `H ```text Located skeleton (thoracic vertebrae T9/T10/T11, classid 0x0A02, mode Located): - FMA10014 00000a02-ce01-fe02-7b02-… ↔ T10,T11,T12,… shared Morton HEEL ce = same spatial octant - FMA10059 00000a02-ce01-d602-eb02-… ↔ T9,T10,T12,… HIP/TWIG descend as the centroid descends (z 1164→1107) + FMA10014 0a020000-ce01-fe02-7b02-… ↔ T10,T11,T12,… shared Morton HEEL ce = same spatial octant + FMA10059 0a020000-ce01-d602-eb02-… ↔ T9,T10,T12,… HIP/TWIG descend as the centroid descends (z 1164→1107) Cascade soft tissue (aortic segments, 0x0A01, mode Cascade): - FMA3736 ascending 00000a01-0901-0702-0e02-… ↔ arch, descending part_of siblings = the connected segments + FMA3736 ascending 0a010000-0901-0702-0e02-… ↔ arch, descending part_of siblings = the connected segments ``` ![FMA skeleton, selected by GUID prefix](docs/graph_skeleton.png) -*`graph … 00000a02` — the canonical key SELECTS the geometry: the `0x0A02` (skeleton) +*`graph … 0a020000` — the canonical key SELECTS the geometry: the `0x0A02` (skeleton) classid prefix renders just the bones as solid triangles (922K), colored by the `tissue` byte (is_a). The address is the render; the prefix is the query. `graph all` / `tissues` / `vessel` render the whole body / inner tissues / vascular tree.* diff --git a/fma/docs/OGAR_CONSUMER_INTEGRATION.md b/fma/docs/OGAR_CONSUMER_INTEGRATION.md index d536f6601..3c9b3876d 100644 --- a/fma/docs/OGAR_CONSUMER_INTEGRATION.md +++ b/fma/docs/OGAR_CONSUMER_INTEGRATION.md @@ -171,7 +171,10 @@ reimplements the canonical 16-byte layout byte-for-byte: - It realizes the **two ontological axes in one key**: each 8:8 HHTL tier is `(place : tissue)` — high byte = PLACE (Morton spatial cell for skeletal nodes classid `0x0A02`, or `part_of` sibling-rank for soft tissue `0x0A01`); low byte - = TISSUE (`is_a` taxonomy sibling-rank). The high-byte chain prefix-routes the + = TISSUE (`is_a` taxonomy sibling-rank). (Order flipped 2026-07-02 — canon HIGH + / custom LOW: `converge.rs` stores these as `0x0A02_0000` / `0x0A01_0000` in the + composed `u32` classid; see `V3_SOA_WIRING.md` §2.1 for the worked GUIDs.) The + high-byte chain prefix-routes the body, the low-byte chain prefix-routes the type taxonomy — **both hierarchies, one key**. `family` (u24) is the `(part_of:is_a)` level-3 ontological basin. `connected_to` lands in the EdgeBlock shape: `part_of` siblings = in-family diff --git a/fma/docs/V3_SOA_WIRING.md b/fma/docs/V3_SOA_WIRING.md index 7a885f26a..1a0f167f8 100644 --- a/fma/docs/V3_SOA_WIRING.md +++ b/fma/docs/V3_SOA_WIRING.md @@ -125,9 +125,14 @@ as u32) << 8) | ia_rank3` — `(part_of:is_a)` one level deeper. `converge.rs` classifies each FMA node into `ConceptDomain::Anatomy` (`0x0A`): -- `classid 0x0000_0A02` — **skeleton** (bone / cartilage / vertebra / rib / femur / +> Order flipped 2026-07-02 — canon HIGH / custom LOW (matches the +> `lance-graph-contract` classid half-order flip; `converge.rs` is dep-free and +> mints this order directly). The pre-flip stored form was `0x0000_0A01` / +> `0x0000_0A02`; every worked example below uses the post-flip form. + +- `classid 0x0A02_0000` — **skeleton** (bone / cartilage / vertebra / rib / femur / skull, via `is_skeletal()`). -- `classid 0x0000_0A01` — **soft tissue** (everything else). +- `classid 0x0A01_0000` — **soft tissue** (everything else). The `place` bytes are then dispatched on classid: @@ -158,8 +163,8 @@ Located skeleton — **thoracic vertebrae T9/T10/T11**, `classid 0x0A02`, mode Located: ```text - FMA10014 00000a02-ce01-fe02-7b02-… ↔ T10,T11,T12,… shared Morton HEEL ce = same spatial octant - FMA10059 00000a02-ce01-d602-eb02-… ↔ T9,T10,T12,… HIP/TWIG descend as the centroid descends (z 1164→1107) + FMA10014 0a020000-ce01-fe02-7b02-… ↔ T10,T11,T12,… shared Morton HEEL ce = same spatial octant + FMA10059 0a020000-ce01-d602-eb02-… ↔ T9,T10,T12,… HIP/TWIG descend as the centroid descends (z 1164→1107) ``` Both vertebrae share `classid 0x0A02` and the **same HEEL high byte `ce`** — they @@ -170,7 +175,7 @@ spatial group. HIP and TWIG diverge as the centroid descends. The low bytes (`01 Cascade soft tissue — **aortic segments**, `classid 0x0A01`, mode Cascade: ```text - FMA3736 ascending aorta 00000a01-0901-0702-0e02-… ↔ arch, descending part_of siblings = the connected segments + FMA3736 ascending aorta 0a010000-0901-0702-0e02-… ↔ arch, descending part_of siblings = the connected segments ``` The ascending aorta, the arch, and the descending aorta are `part_of` siblings of @@ -178,8 +183,8 @@ the same aorta; they share the leading `part_of` high-byte groups (`09`, `07`, ` …) and are connected to each other (§3). The `connected_to` column lists exactly the sibling segments that physically continue the vessel. -The upshot, made visible in `graph.rs`: a single key prefix `00000a01-0901-0702` -selects "one `part_of`/`is_a` subtree" of triangles; `00000a02` selects the whole +The upshot, made visible in `graph.rs`: a single key prefix `0a010000-0901-0702` +selects "one `part_of`/`is_a` subtree" of triangles; `0a020000` selects the whole skeleton. **The address is the query.** --- @@ -216,8 +221,8 @@ value column (`Vec<[u8; 480]>`), separate allocations. Two scans over 1 M synthe rows (seeded by the FMA distribution: ~25 % skeleton classid, the rest soft tissue): - **key-only (prefix-route / render-select):** read only the key column; decode - `classid` from bytes `[0..4)`; count the skeleton subtree (`== 0x0000_0A02`). This - is exactly the work the `/fma-body` skeleton button and `graph 00000a02` do. + `classid` from bytes `[0..4)`; count the skeleton subtree (`== 0x0A02_0000`). This + is exactly the work the `/fma-body` skeleton button and `graph 0a020000` do. - **value (decode the slab):** read the whole value column; sum all 480 bytes per row — the work a value/tenant materialization does. @@ -251,7 +256,7 @@ Why this falls out of the layout, not a trick: `graph.rs` is the same routing at render time, not at scan time: it reads the converged manifest, and a `sel` that is all-hex-or-dash is treated as a GUID prefix — `guid.starts_with(&sel)` selects which meshes' triangles to rasterize. `graph … -00000a02` renders ~922 K skeleton triangles; `graph all` / `tissues` / `vessel` +0a020000` renders ~922 K skeleton triangles; `graph all` / `tissues` / `vessel` render the whole body / inner tissues / vascular tree. The address selects the geometry; the prefix is the query — and it never decodes a value slab to decide. @@ -353,7 +358,7 @@ holds for mechanical/data-shaped leaf methods and remains CONJECTURE until | value tenants / schemas / `classid → ReadMode` | same — `ValueTenant`, `ValueSchema`, `classid_read_mode` | | `(place:tissue)` 8:8 tier, Located/Cascade, mint | `q2/fma/src/bin/converge.rs` | | `connected_to` EdgeBlock from `part_of` siblings | `q2/fma/src/bin/converge.rs` (`siblings`, edge emit) | -| prefix-routed render (`graph 00000a02`) | `q2/fma/src/bin/graph.rs` | +| prefix-routed render (`graph 0a020000`) | `q2/fma/src/bin/graph.rs` | | key-only vs value scan, 89–130×, flat | `q2/fma/src/bin/soa_scan.rs` | | three coexisting v1/v2/v3 addressings | `q2/fma/README.md` | | GUID-is-key, key-prerenders, classid adapters | `OGAR/CLAUDE.md`, `core-first-transcode-doctrine` | diff --git a/fma/src/bin/converge.rs b/fma/src/bin/converge.rs index 1a5e9183f..994fc9826 100644 --- a/fma/src/bin/converge.rs +++ b/fma/src/bin/converge.rs @@ -25,6 +25,11 @@ // `lance_graph_contract::canonical_node::NodeGuid::new(classid, heel, hip, twig, // family, identity)` (OGAR canon, 2026-06-13). classid in ConceptDomain::Anatomy // (0x0A): 0x0A01 soft tissue, 0x0A02 skeleton — same space as the other session's bake. +// (Order flipped 2026-07-02 — canon HIGH / custom LOW: this crate stores the +// canon 0x0A01/0x0A02 value directly in the classid `u32`'s high half, i.e. +// `0x0A01_0000` / `0x0A02_0000`, matching `lance-graph-contract`'s +// `CLASSID_ORDER = CanonHigh`. This crate is dep-free and mints the value +// itself — it does not call the contract's `compose_classid`.) // // usage: converge [inclusion.txt] [isa_inclusion.txt] [out_dir] [parts_dir] [element_parts] // (parts_dir optional — present ⇒ Located skeleton + spatial edges + render-ready @@ -467,9 +472,9 @@ fn main() { let names = po.dn(fma) + " " + &ia.dn(fma); let skeletal_node = is_skeletal(&names); let classid = if skeletal_node { - 0x0000_0A02 + 0x0A02_0000 } else { - 0x0000_0A01 + 0x0A01_0000 }; if skeletal_node { skeletal += 1; diff --git a/fma/src/bin/graph.rs b/fma/src/bin/graph.rs index 5186c5da6..554281ded 100644 --- a/fma/src/bin/graph.rs +++ b/fma/src/bin/graph.rs @@ -5,9 +5,11 @@ // * COLOR — each mesh is colored by its FMA's `tissue` byte (the is_a low byte of // the converged GUID), read straight from `converge`'s manifest. // * SELECT — an optional GUID **prefix** picks which triangles to draw: pass -// `00000a01-0901-0702` and only the parts whose canonical key starts with +// `0a010000-0901-0702` and only the parts whose canonical key starts with // it render (one part_of/is_a subtree). Prefix-routing the key, made // visible — the address selects the geometry. `all` draws the whole body. +// (Prefix example uses the post-2026-07-02-flip classid form — canon +// 0x0A01 in the HIGH half; `converge.rs` mints this order.) // // This is "70k nodes connecting via relationships → 16M triangles": the relationships // (part_of/is_a) ARE the address, and the address drives which triangles light up. @@ -258,7 +260,7 @@ fn main() { tris.len() ); if tris.is_empty() { - eprintln!("[graph] nothing matched '{sel}' — try `all`, a tissue (bone/vessel/...), or a guid prefix like 00000a01-0901"); + eprintln!("[graph] nothing matched '{sel}' — try `all`, a tissue (bone/vessel/...), or a guid prefix like 0a010000-0901"); return; } diff --git a/fma/src/bin/soa_scan.rs b/fma/src/bin/soa_scan.rs index 0731797fd..63ec1b58f 100644 --- a/fma/src/bin/soa_scan.rs +++ b/fma/src/bin/soa_scan.rs @@ -3,9 +3,11 @@ // Proves the OGAR canon's "the key prerenders nodes with ZERO value decode": when the // canonical NodeRow (512 B = key 16 + edges 16 + value 480) is laid out COLUMNAR // (struct-of-arrays) — a contiguous key column + a contiguous value column — a key-only -// scan (prefix-route / render-select, e.g. "draw the skeleton subtree" = classid 0x0A02) +// scan (prefix-route / render-select, e.g. "draw the skeleton subtree" = classid 0x0A02 +// — canon 0x0A02 in the HIGH half of the composed u32, `0x0A02_0000`, since the +// 2026-07-02 half-order flip) // touches ~30x less memory than materializing the value slab, and stays flat as N grows. -// This is the same prefix routing the /fma-body skeleton button and `graph 00000a02` do, +// This is the same prefix routing the /fma-body skeleton button and `graph 0a020000` do, // measured at scale. // // 1M is SYNTHETIC — real FMA is ~1368 placed meshes / ~75K terms; the scan throughput is @@ -17,8 +19,11 @@ use std::hint::black_box; use std::time::Instant; -const CLASSID_SOFT: u32 = 0x0000_0A01; -const CLASSID_SKELETON: u32 = 0x0000_0A02; +// Order flipped 2026-07-02 — canon HIGH / custom LOW (matches the +// lance-graph-contract classid half-order flip; this crate is dep-free and +// mints its own local scheme, kept consistent with the canonical order). +const CLASSID_SOFT: u32 = 0x0A01_0000; +const CLASSID_SKELETON: u32 = 0x0A02_0000; /// Columnar SoA: the 16-byte GUID key column and the 480-byte value-slab column, kept /// in separate contiguous allocations (the "SoA > tenant view" split).