|
| 1 | +# OGAR consumer pre-flight — pull the classid, never re-mint the Core (the consumer spellbook) |
| 2 | + |
| 3 | +> **Operational mirror** of `docs/CONSUMER-BRIDGE-DEPRECATION.md` (the migration |
| 4 | +> recipe, #589/#590) and the **inverse** of OGAR's |
| 5 | +> `SURREAL-AST-TRAP-PREFLIGHT.md` (the producer trap). Read this BEFORE the |
| 6 | +> keyboard fires in any consumer that needs a classid — five questions, 90 |
| 7 | +> seconds, one mirror. |
| 8 | +> |
| 9 | +> Where OGAR's spellbook stops a *producer* substituting SurrealQL AST for |
| 10 | +> `Class` + `ActionDef`, THIS spellbook stops a *consumer* (woa-rs, medcare-rs, |
| 11 | +> smb-office-rs, …) re-implementing the OGAR Core locally instead of pulling |
| 12 | +> from it. Same boundary, opposite arm. |
| 13 | +> |
| 14 | +> Status: **SPELLBOOK v0** (2026-06-22). Append-only. |
| 15 | +> |
| 16 | +> READ BY: `integration-lead`, `baton-handoff-auditor`, `core-first-architect`, |
| 17 | +> the `Plan` subagent — and every consumer-crate session touching a classid, a |
| 18 | +> `*Bridge`, the OGAR codebook, or an `OntologyRegistry`. |
| 19 | +> |
| 20 | +> Grounded in: `docs/CONSUMER-BRIDGE-DEPRECATION.md` (the recipe), |
| 21 | +> `.claude/knowledge/core-first-transcode-doctrine.md` (pull from the Core, |
| 22 | +> never mint a parallel registry), OGAR#95 APP-CLASS-CODEBOOK-LAYOUT (the hi/lo |
| 23 | +> classid split), OGAR#97 `PortSpec::APP_PREFIX` + `render_classid_for`, OGAR#98 |
| 24 | +> `canonical_concept_name`. |
| 25 | +
|
| 26 | +## The trap, in one paragraph |
| 27 | + |
| 28 | +You're a consumer — woa-rs, medcare-rs, smb-office-rs — and you need the classid |
| 29 | +for a domain concept (`Stundenzettel`, `Patient`, `Rechnung`). The first reflex |
| 30 | +is *"construct the bridge: `WoaBridge::new(registry)?.entity(name)?`."* That |
| 31 | +reflex is **System-1** — it reads like a clean lookup. But the bridge carries an |
| 32 | +`OntologyRegistry` it has to hydrate, couples your customer binary to the spine, |
| 33 | +and is `#[deprecated]` as of #589. The **System-2** move is a pure function: |
| 34 | +`WoaPort::class_id(name)` (spine) or `ogar_codebook::canonical_concept_id(name)` |
| 35 | +(membrane). No registry, no hydration, no construction. The deeper trap — the |
| 36 | +one the Core-First doctrine forbids outright — is copying OGAR's codebook *into |
| 37 | +your repo* (a local `const *_CODEBOOK`, a hand-rolled `name → id` map, a parallel |
| 38 | +`class_ids` registry). The Core owns the codebook; a copy drifts the moment OGAR |
| 39 | +mints a concept, and the cross-fork convergence (`Stundenzettel` ⇄ `TimeEntry` ⇄ |
| 40 | +SMB's hours all resolving to `0x0103`) silently breaks. |
| 41 | + |
| 42 | +The trap is silent at write time and loud at the next OGAR codebook bump. |
| 43 | +Avoiding it costs five questions. Recovering from it costs a drift bug nobody |
| 44 | +can see until two forks disagree on a classid. |
| 45 | + |
| 46 | +## The five questions — run BEFORE the keyboard fires |
| 47 | + |
| 48 | +``` |
| 49 | +Q1. WHAT do I need from OGAR? |
| 50 | + ├─ the classid for ONE concept (by name) |
| 51 | + │ → PULL it. *Port::class_id(name) / canonical_concept_id(name). |
| 52 | + │ A pure function returning Option<u16>. Proceed. |
| 53 | + └─ a registry of ALL concepts / the whole codebook / a name↔id map |
| 54 | + → you're about to RE-MINT the Core. STOP. |
| 55 | + The Core is the single source; pull per-concept, don't copy. |
| 56 | +
|
| 57 | +Q2. Am I CONSTRUCTING a bridge object? |
| 58 | + ├─ XBridge::new(registry)?.entity(name)? (registry, hydrate, .entity) |
| 59 | + │ → DEPRECATED (#589). The bridge carries state the pull doesn't |
| 60 | + │ need. STOP. Drop the registry; the classid pull is stateless. |
| 61 | + └─ *Port::class_id(name) (no construction, no &self) |
| 62 | + → correct. Pure function. Proceed. |
| 63 | +
|
| 64 | +Q3. Am I COPYING the codebook / minting a parallel registry? |
| 65 | + ├─ a local `const *_CODEBOOK` / `*_ALIASES`, a hand-rolled |
| 66 | + │ `HashMap<&str,u16>`, or my own `class_ids` mirror |
| 67 | + │ → Core-First VIOLATION. It drifts on the next OGAR mint. STOP. |
| 68 | + └─ pulling from contract::ogar_codebook / lance_graph_ogar |
| 69 | + → correct. One source; the parity-guard fuses drift at build. |
| 70 | +
|
| 71 | +Q4. WHICH CRATE am I pulling from? (the BBB-barrier) |
| 72 | + ├─ customer binary / membrane (woa-rs, medcare-rs realtime, smb-office-rs) |
| 73 | + │ → lance_graph_contract::ogar_codebook ONLY. Zero-dep, BBB-safe. |
| 74 | + │ lance_graph_ogar / -engine / -planner in a customer Cargo.toml |
| 75 | + │ is a BBB BREACH. STOP. |
| 76 | + └─ spine / internal crate |
| 77 | + → lance_graph_ogar::*Port is fine (it re-exports ogar_vocab). |
| 78 | +
|
| 79 | +Q5. Does my classid CARRY the app prefix? |
| 80 | + ├─ render id = APP(hi u16) << 16 | concept(lo u16) (0x0003_0103) |
| 81 | + │ RBAC / ontology key on the shared lo u16 |
| 82 | + │ → correct (OGAR#95 §2; spine: render_classid_for::<P>() #97). |
| 83 | + └─ I'm using the bare lo u16 as the render id, or inventing my own |
| 84 | + prefix scheme |
| 85 | + → drift from the allocation table. STOP. Stamp the prefix. |
| 86 | +``` |
| 87 | + |
| 88 | +Any "STOP" answer catches the trap pre-materialization. |
| 89 | + |
| 90 | +## Diagnostic signatures — what the trap looks like in review |
| 91 | + |
| 92 | +- **`use lance_graph_{ontology,ogar}::bridges::*Bridge`** in a consumer crate, |
| 93 | + followed by `XBridge::new(...)` — the deprecated construction path. |
| 94 | +- **An `OntologyRegistry` / `registry` field on a consumer's own struct** — the |
| 95 | + bridge's hydrate state leaking into the consumer; the pull needs none. |
| 96 | +- **A local `const *_CODEBOOK` / `*_ALIASES`, a hand-rolled `name → classid` |
| 97 | + map, or a `class_ids`-shaped table** copied into the consumer repo — the Core |
| 98 | + copied, not pulled. This is the loud one. |
| 99 | +- **A per-app prefix constant invented locally** (`const APP: u32 = 0x...`) |
| 100 | + instead of `PortSpec::APP_PREFIX` / the OGAR#95 allocation table. |
| 101 | +- **`lance-graph-ogar` / `lance-graph-engine` / `lance-graph-planner` in a |
| 102 | + customer-binary's `Cargo.toml`** — a BBB-barrier breach (Q4). |
| 103 | +- **A consumer-side `entity_type_id()` / `schema_ptr` chain** to get a classid |
| 104 | + the `class_id(name)` pull returns in one call. |
| 105 | + |
| 106 | +One signature is suspicious; a local codebook copy alone is the trap. |
| 107 | + |
| 108 | +## Remediation — three moves (= the `CONSUMER-BRIDGE-DEPRECATION.md` recipe) |
| 109 | + |
| 110 | +1. **Name the concept.** It's the argument to the bridge's `.entity(name)` — |
| 111 | + `Stundenzettel`, `Patient`, `Rechnung`. That string is all you need. |
| 112 | +2. **Pull the classid** — pure function, no registry: |
| 113 | + - spine: `lance_graph_ogar::WoaPort::class_id(name) -> Option<u16>` |
| 114 | + - membrane (BBB): `lance_graph_contract::ogar_codebook::canonical_concept_id(name)` |
| 115 | +3. **Stamp the app prefix + delete the old surface.** Render id = |
| 116 | + `APP << 16 | cid`; authorize on the shared `cid` (lo u16). Then delete: the |
| 117 | + `*Bridge` import, any `OntologyRegistry` field, and every local codebook |
| 118 | + copy. Your diff touches only your crate; the spine is byte-for-byte unchanged. |
| 119 | + |
| 120 | +DoD: no `*Bridge` / `XBridge::new` / local `*_CODEBOOK` survives a grep in your |
| 121 | +repo; the classid pull is a pure function call. |
| 122 | + |
| 123 | +## Consumer status (the worked examples — from #589's snapshot, 2026-06-22) |
| 124 | + |
| 125 | +| Consumer | Files still on `lance_graph_{ontology,ogar}::bridges` | In scope here | |
| 126 | +|---|---|---| |
| 127 | +| MedCare-rs | 33 | yes (`medcare-rs`) | |
| 128 | +| woa-rs | 6 | yes (`woa-rs`) | |
| 129 | +| smb-office-rs | 4 | no | |
| 130 | +| odoo-rs | 0 ✓ | — | |
| 131 | +| openproject-nexgen-rs | 0 ✓ | — | |
| 132 | + |
| 133 | +These three are the migration backlog. The terminal `bridges/` deletion in the |
| 134 | +spine is gated on all three reaching 0. |
| 135 | + |
| 136 | +## A Core gap this spellbook surfaces (honest — flag, don't paper) |
| 137 | + |
| 138 | +`contract::ogar_codebook` mirrors `canonical_concept_id` / `canonical_concept_name` |
| 139 | +(the lo-u16 pull, BBB-safe) but does **not** yet mirror OGAR#97's |
| 140 | +`PortSpec::APP_PREFIX` / `render_classid_for` (the hi-u16 render composition). So |
| 141 | +a **membrane** consumer can pull the shared concept but must hand-stamp the app |
| 142 | +prefix from the OGAR#95 allocation table — a small re-derivation a |
| 143 | +`contract::ogar_codebook::APP_PREFIX` mirror would remove. Per Core-First the |
| 144 | +consumer must NOT hard-code `0x000N`; file the contract mirror (the |
| 145 | +`canonical_concept_name` precedent is OGAR#98) rather than minting a local |
| 146 | +prefix const. Tracked: `ISS-CONTRACT-APP-PREFIX-MIRROR`. |
| 147 | + |
| 148 | +## When this doc fires + trigger phrases |
| 149 | + |
| 150 | +Author (before keyboard): any consumer session that needs a classid, migrates |
| 151 | +off a `*Bridge`, or touches the OGAR codebook. Review (PR landing): any consumer |
| 152 | +PR adding a `*Bridge` import, a local codebook copy, or a `lance-graph-ogar` / |
| 153 | +`-engine` / `-planner` dep to a customer binary. |
| 154 | + |
| 155 | +Triggers: `*Bridge` · `class_id` · `classid` · `entity_type_id` · `codebook` · |
| 156 | +`*_ALIASES` · `OntologyRegistry` · `pull the classid` · `bridge migration` · |
| 157 | +`render classid` · `APP_PREFIX` · BBB-barrier. |
| 158 | + |
| 159 | +## What this doc is NOT |
| 160 | + |
| 161 | +- **Not a ban on `lance-graph-ontology`.** The OGIT cache (TTL/RDF hydration) is |
| 162 | + a legitimate consumer dep; the trap is the *bridge construction* + the *local |
| 163 | + codebook copy*, not the crate. |
| 164 | +- **Not a ban on the bridges existing.** They're `#[deprecated]`, not removed — |
| 165 | + this spellbook governs NEW consumer code and the migration, not a hard cutover. |
| 166 | +- **Not retroactive blame.** The 33/6/4 backlog gets the three-move remediation, |
| 167 | + not a citation. Citations are for future consumer code. |
| 168 | + |
| 169 | +## Cross-refs |
| 170 | + |
| 171 | +- `docs/CONSUMER-BRIDGE-DEPRECATION.md` — the migration recipe (the *what*). |
| 172 | +- `.claude/knowledge/core-first-transcode-doctrine.md` — pull from the Core, |
| 173 | + never mint a parallel registry (the *why*). |
| 174 | +- OGAR `docs/SURREAL-AST-TRAP-PREFLIGHT.md` — the producer-side mirror (the |
| 175 | + *inverse* arm of the same boundary). |
| 176 | +- OGAR#95 `APP-CLASS-CODEBOOK-LAYOUT.md` (hi/lo split) · #97 `render_classid_for` |
| 177 | + · #98 `canonical_concept_name`. |
0 commit comments