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
84 changes: 84 additions & 0 deletions crates/ogar-vocab/src/ports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,45 @@ pub const HUBSPO_ALIASES: &[(&str, u16)] = &[
("Team", class_ids::PROJECT_ACTOR),
];

// ── HIRO (Bardioc-rs, the IT-automation stack) port ──────────────────

/// HIRO's `PortSpec`: the `ogit/_type` strings HIRO puts on the wire onto the
/// `0x0CXX` automation concepts, so a HIRO node dispatches on a `u16` instead
/// of a string match (`docs/HIRO-IN-CLASSES.md` §3.1). `0x000C` is HIRO's
/// render skin. Names are port-scoped: nothing here feeds the global alias
/// normalizer. `ogit/Automation/MAID` has no alias on purpose: MAID has no
/// minted concept, and no workload needs one yet.
pub struct HiroPort;

impl PortSpec for HiroPort {
const NAMESPACE: &'static str = "HIRO";
const BRIDGE_ID: &'static str = "hiro";
const APP_PREFIX: u16 = 0x000C;
fn aliases() -> &'static [(&'static str, u16)] {
HIRO_ALIASES
}
}

/// HIRO's wire names, exactly as its `ogit/_type` field spells them, for the
/// automation concepts.
pub const HIRO_ALIASES: &[(&str, u16)] = &[
("ogit/MARS/Application", class_ids::MARS_APPLICATION),
("ogit/MARS/Resource", class_ids::MARS_RESOURCE),
("ogit/MARS/Software", class_ids::MARS_SOFTWARE),
("ogit/MARS/Machine", class_ids::MARS_MACHINE),
("ogit/Automation/KnowledgeItem", class_ids::KNOWLEDGE_ITEM),
(
"ogit/Automation/MARSNodeTemplate",
class_ids::MARS_NODE_TEMPLATE,
),
("ogit/Automation/ActionHandler", class_ids::ACTION_HANDLER),
(
"ogit/Automation/ActionApplicability",
class_ids::ACTION_APPLICABILITY,
),
("ogit/Automation/Trigger", class_ids::AUTOMATION_TRIGGER),
];

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -759,6 +798,49 @@ mod tests {
assert_eq!(HubSpoPort::class_id("Team"), Some(class_ids::PROJECT_ACTOR));
}

#[test]
fn hiro_classview_composes_canon_high_custom_low() {
assert_eq!(HiroPort::NAMESPACE, "HIRO");
assert_eq!(HiroPort::BRIDGE_ID, "hiro");
assert_eq!(HiroPort::classview(), 0x000C);
assert_eq!(HiroPort::class_id("ogit/MARS/Machine"), Some(0x0C04));
assert_eq!(HiroPort::class_id("ogit/Automation/Trigger"), Some(0x0C09));
let machine = render_classid(HiroPort::APP_PREFIX, class_ids::MARS_MACHINE);
assert_eq!(machine, 0x0C04_000C);
assert_eq!(concept_of(machine), 0x0C04);
assert_eq!(app_of(machine), 0x000C);
// The wire name is exact: no case folding, no OGIT CURIE spelling.
assert_eq!(HiroPort::class_id("ogit/mars/machine"), None);
assert_eq!(HiroPort::class_id("ogit.MARS:Machine"), None);
// MAID stays unminted, so HIRO's MAID type does not resolve.
assert_eq!(HiroPort::class_id("ogit/Automation/MAID"), None);
}

/// The port covers exactly the automation domain: every `0x0CXX` concept
/// has its HIRO wire name, and no alias leaves the domain. A tenth
/// automation concept fails this until it gets a wire name or a reason
/// not to have one.
#[test]
fn hiro_covers_exactly_the_automation_domain() {
use crate::{ConceptDomain, canonical_concept_domain, concepts_in_domain};
let domain: Vec<(&str, u16)> = concepts_in_domain(ConceptDomain::Automation).collect();
assert_eq!(domain.len(), 9);
for (concept, id) in &domain {
assert!(
HIRO_ALIASES.iter().any(|(_, a)| a == id),
"{concept} (0x{id:04X}) has no HIRO wire name",
);
}
for (name, id) in HIRO_ALIASES {
assert_eq!(
canonical_concept_domain(*id),
ConceptDomain::Automation,
"{name} resolves outside the automation domain",
);
}
assert_eq!(HIRO_ALIASES.len(), domain.len());
}

#[test]
fn spear_classview_composes_canon_high_custom_low() {
assert_eq!(SpearPort::class_id("Email"), Some(0x0B05));
Expand Down Expand Up @@ -1390,6 +1472,7 @@ mod tests {
WeatherNextPort::APP_PREFIX,
SpearPort::APP_PREFIX,
HubSpoPort::APP_PREFIX,
HiroPort::APP_PREFIX,
] {
assert_ne!(
prefix, 0x1000,
Expand Down Expand Up @@ -1476,6 +1559,7 @@ mod tests {
WeatherNextPort::APP_PREFIX,
SpearPort::APP_PREFIX,
HubSpoPort::APP_PREFIX,
HiroPort::APP_PREFIX,
];
let mut sorted = prefixes;
sorted.sort_unstable();
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 @@ -183,6 +183,7 @@ materialised until the app mints its first private class).
| `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 |
| `0x000B` | HubSPO / hubspo-rs (CRM) | `0x05` crm (15 concepts) + aliases onto `0x0B05` email, `0x0207` product, `0x0201` line item, `0x0202` commercial document, `0x0205` payment record, `0x0203` tax policy, `0x0104` project actor | **no** — maps onto core |
| `0x000C` | HIRO / Bardioc-rs (IT automation) | `0x0C` automation: HIRO's nine `ogit/_type` wire names (`ogit/MARS/Application` … `ogit/Automation/Trigger`) onto `0x0C01`–`0x0C09`; `ogit/Automation/MAID` unaliased, no concept minted | **no** — maps onto core |
| `0x00A0` | (reserved) future app block | — | — |

> **OpenProject (`0x0001`) and Redmine (`0x0007`) are the showcase:**
Expand Down
6 changes: 6 additions & 0 deletions docs/DISCOVERY-MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ two halves of a cell. ADR‑026 names the cascade that ties them.
|---|---|:--:|:--:|---|---|
| D‑CRM‑DOMAIN | class domain byte `0x05` = **CRM**: 15 concepts `crm_contact 0x0501` … `crm_custom_record 0x050F` (contact, company, deal, ticket, lead, pipeline, pipeline stage, call, meeting, task, note, list, campaign, sequence, custom record). Each passed METHOD ∧ STORAGE ∧ STRUCTURE (STRUCTURE = rendered as a distinct item type on some screen; [H] until the consumer's screen‑graph oracle runs). Ten HubSpot objects **converge** onto existing core concepts instead of minting (email, product, line item, quote/invoice/order → commercial document, payment, tax, user/team → project actor). Custom objects and custom properties are tenant data: they share the one `crm_custom_record` concept and are never minted. **Not the recipe byte:** `RecipeConceptId` family `0x05` (Scope) is a separate typed address space and stays reserved mint‑on‑emit (E‑RECIPE‑FAMILIES‑MINT‑ON‑EMIT). `crm_actions` is **deferred**: no capability rows are declared until the consumer implements them (the `geo_actions` rule — declared surface nobody implements is fiction). | G (codebook + port + tests) / H (STRUCTURE axis) | CODED | `crates/ogar-vocab` (`CODEBOOK`, `ConceptDomain::Crm`, `class_ids::CRM_*`, `ports::HubSpoPort`); mirror in lance‑graph `ogar_codebook.rs` | APP‑CLASS‑CODEBOOK‑LAYOUT §2; HOTPLUG‑MIGRATION‑GUIDE |

### 2.13 HIRO port (2026‑10‑10) — consumer Bardioc-rs, port `0x000C`

| ID | Shape | Grade | Status | Home | Deps |
|---|---|:--:|:--:|---|---|
| D‑HIRO‑PORT | `HiroPort` at app prefix `0x000C` maps the `ogit/_type` strings HIRO puts on the wire (`ogit/MARS/Application`, `/Resource`, `/Software`, `/Machine`; `ogit/Automation/KnowledgeItem`, `/MARSNodeTemplate`, `/ActionHandler`, `/ActionApplicability`, `/Trigger`) onto the nine `0x0CXX` automation concepts, so a HIRO node dispatches on a `u16` instead of a string match (`HIRO-IN-CLASSES.md` §3.1). **Nothing is minted.** `ogit/Automation/MAID` deliberately has no alias: MAID has no concept and no workload needs one, and a row minted before its falsifier passes is enum explosion (`ogar-r2il`). **DECISION (2026‑10‑10):** Bardioc-rs meets spear through ports, not code — spear's only public contract is `SpearPort` (`0x000A`), so both sides share classids. SCOPE: the port only; no HIRO capability rows. REVISIT WHEN: a workload needs MAID, or a tenth automation concept is minted (the coverage test fails until it gets a wire name). | G (port + tests) | CODED | `crates/ogar-vocab` (`ports::HiroPort`, `HIRO_ALIASES`); lance‑graph mirror `AppPrefix::Hiro` in `ogar_codebook.rs` | HIRO‑IN‑CLASSES §3.1; APP‑CLASS‑CODEBOOK‑LAYOUT §2 |

---

## 3. The materialization pipeline — what's ready, what's blocked
Expand Down
Loading