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
8 changes: 4 additions & 4 deletions .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ Plan: `.claude/plans/v3-mandatory-hotplug-reading-v1.md`.

| D-id | Deliverable | Status | Gate |
|---|---|---|---|
| **D-HPR-0** | Ruling + plan + caller inventory | In PR (#1464) | — |
| **D-HPR-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | In PR (#1464; 4 `_with`/`_plugged` forms, disable runs 4/4 red) | new form returns the handed reading even where `classid_read_mode` disagrees |
| **D-HPR-2** | Canon domain readings move into `OgarAuthority::concept_override` | Queued | `read_mode_for(concept)` == today's `classid_read_mode(<V3 classid>)` per domain |
| **D-HPR-0** | Ruling + plan + caller inventory | Shipped (#1464) | — |
| **D-HPR-1** | Contract modules (`ocr`, `aiwar`, `nan_projection`, `soa_graph`) take the reading as a parameter | Shipped (#1464; 4 `_with`/`_plugged` forms, disable runs 4/4 red) | new form returns the handed reading even where `classid_read_mode` disagrees |
| **D-HPR-2** | Canon domain readings move into `OgarAuthority::concept_override` | In PR (5 concept arms; red with an arm removed) | `read_mode_for(concept)` == today's `classid_read_mode(<V3 classid>)` per domain |
| **D-HPR-3** | Consumers move (lance-graph, q2, MedCare); `classid_read_mode` deprecated | Queued | no production caller left |
| **D-HPR-4** | `0x1000` / render prefixes documented as deprecated, kept (no eviction) | In PR (#1464; doc notes on `CLASSID_*_V3` + `classid_read_mode`) | — |
| **D-HPR-4** | `0x1000` / render prefixes documented as deprecated, kept (no eviction) | Shipped (#1464; doc notes on `CLASSID_*_V3` + `classid_read_mode`) | — |
| **D-HPR-5** | Stored legacy keys keep resolving; a `SlabDeclaration` may override for its own data; no forced removal | Queued | — |
| **D-HPR-6** | No V1 default for an unknown classid; `guid-v3-tail` not a gate | Queued | unknown classid → `NoReadingFor` |

Expand Down
3 changes: 2 additions & 1 deletion .claude/plans/v3-mandatory-hotplug-reading-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ What it means, in rules:
- [x] **W1 — D-HPR-1: contract takes the reading as a parameter.** `ocr`, `aiwar`, `nan_projection`, `soa_graph` get variants that take a `ReadMode` (or a `ResolvedReading`) instead of looking the classid up. `DomainSpec` carries its reading. The classid-lookup forms stay, documented as legacy, until W3. Additive: no consumer breaks.
- Falsifier: a test per module that the new form yields the reading it was handed, including one where the handed reading differs from what `classid_read_mode` would return (so a caller that silently re-looks-up fails).
- **Shipped:** `ocr::LayoutBlock::to_node_row_with`, `aiwar::aiwar_node_rows_with`, `soa_graph::{project_snapshot_with, nearest_anchor_with}`, `nan_projection::project_energy_nonfinite_plugged` (resolves each run through `Activation::resolve_tenant_reading`; an unplugged concept is `NoReadingFor`). Tests use classid `0x1718_0000`, which the lookup reads as V1 / `Full`; each is red when its form is changed to re-look-up (disable runs, 4/4).
- [ ] **W2 — D-HPR-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved.
- [x] **W2 — D-HPR-2: the canon domains get their reading from the authority.** The domain value models (`OSINT`/`PROJECT`/`ERP` Cognitive, `FMA`/`CPIC` Compressed) move into `concept_override`, keyed by concept, all on a V3 tail. `OgarAuthority` then answers for those concepts with no classview involved.
- **Shipped:** `lance-graph-ogar::concept_override` arms `0x0701`/`0x0A01`/`0x0E01`/`0x0101`/`0x0202` → `ReadMode::{OSINT,FMA,CPIC,PROJECT,ERP}_V3`. Test `canon_domains_read_from_the_plug_as_the_classid_table_reads_them` pins each equal to `classid_read_mode(CLASSID_*_V3)`; red with an arm removed. No consumer plugs these concepts today (`grep` of every `HotPlug` in lance-graph, OGAR, MedCare-rs, q2, odoo-rs, openproject-nexgen-rs, HubSPO-rs, spear), so no live reading changes.
- Falsifier: `read_mode_for(concept)` equals today's `classid_read_mode(<V3 classid>)` for each canon domain, so the move changes no reading.
- [ ] **W3 — D-HPR-3: consumers move.** lance-graph callcenter / deepnsm-v2 / probes, then q2 osint-bake + geo, MedCare cohorts. Each takes its reading from its `Activation`. Then `classid_read_mode` gets `#[deprecated]`.
- [x] **W4 — D-HPR-4: document the fossils as deprecated.** Done in `lance-graph-contract`: the five `CLASSID_*_V3` constants and `classid_read_mode` carry a "Deprecated, kept" doc note. A doc note, not `#[deprecated]`: the attribute would turn every existing caller red under `-D warnings`, which is the forced migration the ruling rules out. OGAR (`ogar-osm` `CLASSVIEW_V3_SUBSTRATE`, ro, dismech, loco) and q2 `geo` get the same note when next touched; existing mints stay.
Expand Down
113 changes: 111 additions & 2 deletions crates/lance-graph-ogar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,16 @@ const fn concept_override(classid: u16) -> Option<ReadMode> {
value_schema: ValueSchema::Bootstrap,
edge_codec: EdgeCodecFlavor::CoarseOnly,
}),
// The canon domains' value models, keyed by concept (D-HPR-2, plan
// `v3-mandatory-hotplug-reading-v1`). The same readings
// `classid_read_mode` gives their `CLASSID_*_V3` keys, so a consumer
// plugging one of these concepts reads what the classid table reads,
// with no classview involved.
0x0701 => Some(ReadMode::OSINT_V3),
0x0A01 => Some(ReadMode::FMA_V3),
0x0E01 => Some(ReadMode::CPIC_V3),
0x0101 => Some(ReadMode::PROJECT_V3),
0x0202 => Some(ReadMode::ERP_V3),
Comment thread
AdaWorldAPI marked this conversation as resolved.
_ => None,
}
}
Expand All @@ -495,6 +505,29 @@ const fn is_palette_seat(classid: u16) -> bool {
classid >= 0x1717 && (classid >> 8) == 0x17
}

/// `true` for the five canon storage domains whose reading lives in
/// [`concept_override`] (D-HPR-2): OSINT `0x0701`, FMA `0x0A01`, CPIC
/// `0x0E01`, PROJECT `0x0101`, ERP `0x0202`.
const fn is_canon_storage_domain(classid: u16) -> bool {
matches!(classid, 0x0701 | 0x0A01 | 0x0E01 | 0x0101 | 0x0202)
}

/// A canon storage domain that the capability join does not serve, so it is
/// plugged for its reading alone (codex P1 on #1465). The join answers
/// `UnknownClassid` for a concept outside the capability codebook and
/// `NoCapabilitiesFor` for one with no action table; either way there is no
/// capability side, and routing it to the join would refuse the whole plug.
/// Probed per id rather than listed, so a domain that later gains actions
/// goes back through the join and its capabilities are not dropped.
fn is_reading_only_canon_domain(consumer: &str, classid: u16) -> bool {
use ogar_vocab::capability_registry::{resolve_hotplug, HotplugDrift};
is_canon_storage_domain(classid)
&& matches!(
resolve_hotplug(consumer, &[classid], &[]),
Err(HotplugDrift::UnknownClassid(_) | HotplugDrift::NoCapabilitiesFor(_))
)
}

/// The consumer a palette seat is KNOWN to belong to, when one is known.
///
/// Deliberately a short list of CLAIMED seats, not a roster of participants.
Expand Down Expand Up @@ -571,8 +604,12 @@ impl lance_graph_contract::hotplug::CapabilityAuthority for OgarAuthority {
// The two id kinds answer to different authorities, so they are
// routed separately and the results merged; the reading covers the
// whole plug either way, because it is derived from the plug.
let (palette, capability): (Vec<u16>, Vec<u16>) =
plug.classids.iter().partition(|&&id| is_palette_seat(id));
// Canon storage domains with no capability side are reading-only too
// (codex P1 on #1465): the join refuses them, so they are routed with
// the palette seats and get their reading from `plug_readings`.
let (palette, capability): (Vec<u16>, Vec<u16>) = plug.classids.iter().partition(|&&id| {
is_palette_seat(id) || is_reading_only_canon_domain(plug.consumer, id)
});

// A CLAIMED seat is its owner's, whichever arm it arrives on. An
// unclaimed one is plug-and-play for whoever plugs it.
Expand Down Expand Up @@ -783,6 +820,78 @@ mod plug_and_play_reading {
assert!(super::OgarAuthority.activate(&BLOCKLY).is_ok());
}

/// D-HPR-2: the canon domains' readings come from the plug, keyed by
/// concept, and equal what `classid_read_mode` gives their `*_V3` keys.
#[test]
fn canon_domains_read_from_the_plug_as_the_classid_table_reads_them() {
use lance_graph_contract::canonical_node::NodeGuid;
let domains = [
(0x0701u16, NodeGuid::CLASSID_OSINT_V3),
(0x0A01, NodeGuid::CLASSID_FMA_V3),
(0x0E01, NodeGuid::CLASSID_CPIC_V3),
(0x0101, NodeGuid::CLASSID_PROJECT_V3),
(0x0202, NodeGuid::CLASSID_ERP_V3),
];
let plug = HotPlug {
consumer: "canon-domains",
classids: &[0x0701, 0x0A01, 0x0E01, 0x0101, 0x0202],
covered: &[],
};
// Through the public path, not the private helper (codex P1 on
// #1465): every one of these ids is refused by the capability join,
// so a reading reachable only via `plug_readings` would be dead.
let act = super::OgarAuthority
.activate(&plug)
.expect("a canon-domain plug activates for its reading");
let readings = act.declared_readings().to_vec();
for (concept, classid) in domains {
assert_eq!(
classid >> 16,
u32::from(concept),
"fixture pairs concept and classid"
);
let from_plug = readings
.iter()
.find_map(|(c, m)| (*c == concept).then_some(*m))
.expect("every plugged concept reads");
assert_eq!(from_plug, classid_read_mode(classid), "0x{concept:04X}");
assert_eq!(from_plug.tail_variant, TailVariant::V3);
}
// Anti-vacuity: the domains do not all read the plug-and-play default,
// so the overrides are what this test checks.
assert!(domains.iter().any(|(c, _)| readings
.iter()
.any(|(rc, m)| rc == c && *m != ReadMode::PLUG_AND_PLAY_V3)));
}

/// A canon storage domain plugged alongside capability ids: the domain
/// gets its reading without reaching the join, and the capability half
/// still resolves (codex P1 on #1465).
#[test]
fn a_canon_domain_next_to_capability_ids_activates_and_reads_its_domain() {
let mixed = HotPlug {
consumer: "medcare-rs",
classids: &[0x0A01, 0x0901, 0x0902],
covered: &[
"register_patient",
"get_patient_record",
"list_patients",
"update_patient_access",
"add_diagnosis",
"get_diagnosis",
"list_diagnoses",
"delete_diagnosis",
],
};
let act = super::OgarAuthority
.activate(&mixed)
.expect("the canon domain must not make the plug refuse");
assert_eq!(act.read_mode_for(0x0A01), Ok(ReadMode::FMA_V3));
assert_eq!(act.read_mode_for(0x0901), Ok(ReadMode::PLUG_AND_PLAY_V3));
assert!(!act.capabilities.is_empty());
assert!(act.concepts.iter().all(|(_, id)| *id != 0x0A01));
}

/// THE MEDCARE CASE — the one this rewrite exists for.
///
/// A capability consumer outside `0x17XX` is as plugged in as a palette,
Expand Down
Loading