|
| 1 | +# Hot-Plug Consumer Migration — the generic plug-and-play pattern |
| 2 | + |
| 3 | +> READ BY: any session migrating a consumer (woa-rs, medcare-rs, |
| 4 | +> smb-office-rs, odoo-rs, tesseract-rs, osm-website-rs, …) onto the |
| 5 | +> OGAR-authoritative capability surface; any session adding a new |
| 6 | +> authoritative domain table (thinking-styles best practices, …); |
| 7 | +> integration-lead / baton-handoff-auditor before any cross-repo |
| 8 | +> capability wiring. |
| 9 | +> |
| 10 | +> Shipped 2026-07-07 (operator-ruled same day). Reference PRs: |
| 11 | +> OGAR #174 (ocr_actions) + #175 (resolve_hotplug), lance-graph #658 |
| 12 | +> (hotplug socket + OgarAuthority + CI fuse gate), tesseract-rs #13/#14 |
| 13 | +> (executor + HOT_PLUG migration — the template consumer). |
| 14 | +
|
| 15 | +## The model — three roles, three homes, one binary |
| 16 | + |
| 17 | +Everything links into ONE binary (lance-graph + OGAR + consumer + |
| 18 | +ndarray). Nothing serializes; every check is a compile-time const or a |
| 19 | +test in the consumer's own suite. "Wenn's knallt, dann einmal — nicht |
| 20 | +200 Pins monitoren." |
| 21 | + |
| 22 | +| Role | Home | Surface | |
| 23 | +|---|---|---| |
| 24 | +| **SOCKET** (agnostic, zero-dep) | `lance_graph_contract::hotplug` | `HotPlug { consumer, classids, covered }`, `Activation`, `ActivationDrift`, trait `CapabilityAuthority` | |
| 25 | +| **AUTHORITY** (declares + resolves) | OGAR `ogar_vocab` | domain action tables (e.g. `ocr_actions`) + `capability_registry::{domain_tables, resolve_hotplug, HotplugDrift}` | |
| 26 | +| **BRIDGE** (socket ⇄ authority) | `lance-graph-ogar` (workspace-EXCLUDED) | `OgarAuthority: CapabilityAuthority`; also owns COUNT_FUSE + per-entry mirror parity + the roundtrip green light | |
| 27 | +| **CONSUMER** | your repo | ONE `HOT_PLUG` const + ONE activation test + your executor | |
| 28 | + |
| 29 | +The classid is the join key on BOTH sides: the consumer says "these |
| 30 | +canon-high ids are hot", the authority returns BOTH the vocab rows and |
| 31 | +every capability whose subject is one of those ids. |
| 32 | + |
| 33 | +## Migration recipe (per consumer, ~1 hour) |
| 34 | + |
| 35 | +1. **Authority side (OGAR PR #1):** declare your domain's action table |
| 36 | + in `ogar-vocab` next to `ocr_actions` — one `ActionDef` per |
| 37 | + capability on YOUR already-minted canon-high concepts (mint via the |
| 38 | + normal codebook process if missing; hi u16 = concept, lo u16 = APP |
| 39 | + render prefix, NEVER a shape ordinal). Export `<DOMAIN>_ACTION_NAMES` |
| 40 | + (const fingerprint), `<DOMAIN>_SUBJECT_CLASSIDS`, |
| 41 | + `<DOMAIN>_EXPECTED_EXECUTORS = ["<your-crate>"]`. Register ONE entry |
| 42 | + in `capability_registry::domain_tables()` mapping |
| 43 | + `(capability, subject classid)`. That's the whole authority change. |
| 44 | +2. **Consumer side:** add path deps (NO PINS — local siblings): |
| 45 | + `ogar-vocab = { path = "<rel>/OGAR/crates/ogar-vocab" }` and |
| 46 | + `lance-graph-contract = { path = "<rel>/lance-graph/crates/lance-graph-contract" }`. |
| 47 | +3. Declare the plug: |
| 48 | + ```rust |
| 49 | + pub const HOT_PLUG: lance_graph_contract::hotplug::HotPlug = |
| 50 | + lance_graph_contract::hotplug::HotPlug { |
| 51 | + consumer: "<your-crate>", |
| 52 | + classids: ogar_vocab::<domain>_actions::<DOMAIN>_SUBJECT_CLASSIDS, |
| 53 | + covered: COVERED_CAPABILITIES, // your executor's arms |
| 54 | + }; |
| 55 | + ``` |
| 56 | +4. Close the loop with ONE test: |
| 57 | + ```rust |
| 58 | + let (concepts, capabilities) = ogar_vocab::capability_registry::resolve_hotplug( |
| 59 | + HOT_PLUG.consumer, HOT_PLUG.classids, HOT_PLUG.covered, |
| 60 | + ).expect("hot-plug drifted from the authoritative OGAR tables"); |
| 61 | + ``` |
| 62 | + (Or through the socket: `OgarAuthority.activate(&HOT_PLUG)` via |
| 63 | + `dyn CapabilityAuthority` when you want authority-agnostic code.) |
| 64 | +5. **CI:** your workflows need the sibling checkouts (OGAR + |
| 65 | + lance-graph), same as tesseract-rs `rust.yml` / lance-graph |
| 66 | + `rust-test.yml`. Sibling layout: check the repo out into a subdir |
| 67 | + (`path: <repo>`) so `path: OGAR` lands as a true sibling. |
| 68 | + |
| 69 | +## The drift arms (each one named bang, test-time, in YOUR binary) |
| 70 | + |
| 71 | +| Arm | Meaning | |
| 72 | +|---|---| |
| 73 | +| `UnknownClassid(id)` | plugged id not minted in the codebook | |
| 74 | +| `NoCapabilitiesFor(id)` | id resolves to no capability — the table/ontology was forgotten | |
| 75 | +| `UnexpectedConsumer` | you are not in the table's expected-executor list | |
| 76 | +| `Uncovered(cap)` | authority declares it, your executor has no arm | |
| 77 | +| `Undeclared(cap)` | your executor claims it, authority doesn't declare it | |
| 78 | + |
| 79 | +Plus, CI-side (lance-graph `rust-test.yml`): COUNT_FUSE + per-entry |
| 80 | +mirror parity + the roundtrip green light run against the real OGAR |
| 81 | +sibling on every push. |
| 82 | + |
| 83 | +## What NOT to do (each burned once, 2026-07-07) |
| 84 | + |
| 85 | +- **No bespoke per-consumer plug crate/mechanism.** The registration |
| 86 | + surface is the `HOT_PLUG` const + one test. (tesseract-ogar's first |
| 87 | + `CapabilityRegistration` draft was replaced for exactly this.) |
| 88 | +- **No path/optional deps on `lance-graph-contract` toward OGAR.** The |
| 89 | + contract is a workspace MEMBER; any path dep there (even optional) is |
| 90 | + resolved at workspace-load time and kills every CI cargo invocation. |
| 91 | + The contract stays zero-dep; armed things live in excluded crates. |
| 92 | +- **No shape ordinals in the classid low u16.** Low half = APP render |
| 93 | + prefix (`PortSpec::APP_PREFIX`). Distinct shapes are distinct hi-u16 |
| 94 | + concepts or ClassView payload readings. |
| 95 | +- **No ontology payload in lance-graph.** lance-graph carries the wire |
| 96 | + mirror + the green light, nothing else. Authority checks live in OGAR. |
| 97 | +- **No git deps on OGAR.** git+branch always writes a rev pin into |
| 98 | + Cargo.lock. Path deps to the sibling, always. |
| 99 | + |
| 100 | +## Future synergies (evaluated 2026-07-07 — next arcs, not shipped) |
| 101 | + |
| 102 | +1. **ActionDef plug-and-play from ruff (HIGH, near-term).** |
| 103 | + `ogar-from-ruff::lift_actions` already lifts ActionDefs from |
| 104 | + Rails/Python model graphs — the OCR table is hand-declared only |
| 105 | + because tesseract has no AR source. The generic path: ruff harvest |
| 106 | + (`ruff_*_spo`) → fuzzy-recipe codebook (`(verb, criteria)` recipes, |
| 107 | + ruff `.claude/knowledge/fuzzy-recipe-codebook.md`) → `lift_actions` → |
| 108 | + the domain table auto-derives instead of being hand-written; a |
| 109 | + consumer migration then starts with a harvest, not an authorship |
| 110 | + session. Missing piece: a C++ arm for `lift_actions` (ruff_cpp_spo |
| 111 | + has walk_tu/walk_free_functions/walk_enums; an action-lift over the |
| 112 | + method-body facts is the natural fourth arm) and a |
| 113 | + `derive_domain_table!`-style bridge from lifted ActionDefs into |
| 114 | + `domain_tables()`. |
| 115 | +2. **Param-enum fidelity via `walk_enums` (MEDIUM).** Capability params |
| 116 | + that are C/C++ enums (PSM modes, connectivity 4/8, …) can carry |
| 117 | + their variants from the ruff enum harvest instead of free-form |
| 118 | + names — the same shapes-from-ruff discipline the dawg/dict arc used. |
| 119 | +3. **Ontology plug-and-play (MEDIUM, design needed).** Today |
| 120 | + `resolve_hotplug` returns vocab rows + capability names. The same |
| 121 | + join can return ONTOLOGY fragments per classid: OGAR's |
| 122 | + `vocab/exports → OGIT promote → vocab/imports` pipeline already |
| 123 | + stages TTL per domain, and `ogar-adapter-ttl` renders it. A |
| 124 | + `resolve_hotplug_with_ontology` returning the OGIT NTO fragment |
| 125 | + references for the plugged classids would let a consumer pull |
| 126 | + concepts + actions + ontology in one activation — still zero |
| 127 | + serialization in-binary; the TTL stays an OGAR-internal artifact |
| 128 | + (imports/ tier), never hauled through lance-graph. Gate: needs the |
| 129 | + imports/ tier populated for the domain (ontologies, not harvests — |
| 130 | + harvests stay in the consumer repo). |
| 131 | +4. **Thinking-styles best practices (HIGH, planned all along).** One |
| 132 | + `domain_tables()` entry + a `<STYLES>_EXPECTED_EXECUTORS` list arms |
| 133 | + the thinking-style table for whichever engine executes styles — |
| 134 | + same recipe as step 1 above, zero new machinery. |
0 commit comments