From d0806bd6393a1872529da21bf9b8a32a23278751 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 19:21:34 +0000 Subject: [PATCH] docs(OGAR): consumer best-practices guide + classid-is-address precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coupled sharpenings of the consumer-side doctrine, born from cross- session corroboration on the GUID layout question: 1) New OGAR-CONSUMER-BEST-PRACTICES.md — the muscle-memory guide. Pattern-centric, example-heavy. Drilled with worked classid values across every consumer (medcare 0x0005_0901, woa 0x0003_0103, smb 0x0004_0204, odoo 0x0002_0103, openproject 0x0001_0102, redmine 0x0007_0102, the 5-way BILLABLE_WORK_ENTRY convergence pin): §0 the one-line invariant + three drilled corollaries §1 address anatomy with concrete hi/lo examples + APP allocation table + domain byte table §2 the four canonical patterns: Pattern 1 — pull classid (Port::class_id) Pattern 2 — compose render classid (APP_PREFIX | concept) Pattern 3 — authorize by classid (interim until keystone) Pattern 4 — distill/migrate from legacy bridge (4a one-line import repoint; 4b full structural drop) Each pattern shown with ✅ canonical + ❌ anti shapes. §3 anti-pattern catalogue paired with right-shape references §4 worked migration narrative — Medcare patient end-to-end §5 activation triggers + agents that load Tier-1 §6 cross-refs (incl. lance-graph #591 parallel-session sibling) 2) SURREAL-AST-TRAP-PREFLIGHT.md §0 — sharpens the spellbook with the precision from lance-graph #591: "the classid is pure address; the magic is what it resolves to." Explains why the trap is structurally impossible to fix from inside DDL — SurrealQL has the address but not the Core types behavior resolves to, so encoding lifecycle in DEFINE EVENT is putting the value into the key. Cross-link to best- practices guide added to §Cross-refs. 3) CLAUDE.md — registered best-practices guide as doc-family #8; added matching non-negotiable ("Consumer call sites: classid is address, magic is at the resolution target") under the existing SurrealQL non-negotiable. 4) APP-CLASS-CODEBOOK-LAYOUT.md — top-of-doc cross-ref to the new muscle-memory guide; reinforces hi-u16 = render magic ≠ class magic distinction. Companions to lance-graph #591 (parallel session's `.claude/knowledge/ogar-consumer-preflight.md` + ISS-CONTRACT-APP- PREFIX-MIRROR Core-gap entry) — same spellbook pattern, cast in two domains. Two independent sessions converging on the address-vs-magic precision is the muscle the two docs together drill across every future consumer-side session. Doc-only PR; no code touched. Co-Authored-By: Claude Opus 4.7 Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP --- CLAUDE.md | 18 ++ docs/APP-CLASS-CODEBOOK-LAYOUT.md | 7 + docs/OGAR-CONSUMER-BEST-PRACTICES.md | 386 +++++++++++++++++++++++++++ docs/SURREAL-AST-TRAP-PREFLIGHT.md | 47 ++++ 4 files changed, 458 insertions(+) create mode 100644 docs/OGAR-CONSUMER-BEST-PRACTICES.md diff --git a/CLAUDE.md b/CLAUDE.md index 21b31cd..783cdbf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -189,6 +189,15 @@ alignment costs. Until measured: 3×4 stands. Operational pre-flight: `docs/SURREAL-AST-TRAP-PREFLIGHT.md` (the spellbook — five questions, 90 seconds, MANDATORY before any producer→IR / transcode / codegen / `.surql` authoring session). +8. `docs/OGAR-CONSUMER-BEST-PRACTICES.md` — the muscle-memory guide + for every consumer-side session (medcare-rs, woa-rs, smb-office-rs, + odoo-rs, openproject-nexgen-rs, q2). One-line invariant: **the + classid is pure address; the magic is what it resolves to.** Drilled + with worked examples across every PortSpec, the four canonical + patterns (pull classid · compose render · authorize · migrate), and + an anti-pattern catalogue paired with the right shapes. MANDATORY + before authoring any consumer call site that mentions `class_id`, + `APP_PREFIX`, `PortSpec`, `ClassView`, or any `*Bridge`/`UnifiedBridge`. 8. `docs/ARCHITECTURAL-DECISIONS-2026-06-04.md` — ADR-001..025 (ADR-026 pending). 9. `.claude/agents/` — the 5+3 hardening pattern (5 research savants + @@ -207,6 +216,15 @@ alignment costs. Until measured: 3×4 stands. … THEN …` carrying lifecycle is the "negative-beauty hijack" `SURREAL-AST-AS-ADAPTER.md` §0 rejects. Behavior flows producer → OGAR `Class`+`ActionDef` → adapter; never producer → DDL. +- **Consumer call sites: classid is address, magic is at the + resolution target:** before authoring consumer code that pulls a + classid, composes a render address, authorizes, or migrates off a + bridge, read `docs/OGAR-CONSUMER-BEST-PRACTICES.md` (the muscle- + memory guide). The hi u16 chooses **render** skin (per-app + ClassView/template), the lo u16 names the **shared concept** (RBAC + + ontology); **neither half carries behavior** — class-magic + (`ActionDef`+`KausalSpec`) is a property of the Core node the + address resolves to, never of the address. - **PII:** never emit German PII labels (medcare-rs leaf-rename at the adapter is the guarantee). Word-boundary abort-guard before commit. - **No model identifier** in any committed artifact (chat only). diff --git a/docs/APP-CLASS-CODEBOOK-LAYOUT.md b/docs/APP-CLASS-CODEBOOK-LAYOUT.md index 2a17036..46c7411 100644 --- a/docs/APP-CLASS-CODEBOOK-LAYOUT.md +++ b/docs/APP-CLASS-CODEBOOK-LAYOUT.md @@ -9,6 +9,13 @@ > prefix** and pins the rule that keeps "classid is shared currency" > intact. > +> **Read together with** `docs/OGAR-CONSUMER-BEST-PRACTICES.md` — the +> muscle-memory guide with worked examples across every consumer. **The +> classid is pure address (both halves)**; behavior lives at the +> resolution target (ClassView for the skin / `Class`+`ActionDef` for +> the canonical shape and magic). Hi u16 selects **render** magic, NOT +> class magic; class magic is the Core's, never the address's. +> > **The goal it serves (§3.5–3.7):** every renderable thing — strings, > text, media, online sources — is rendered by **key-value resolution** > against typed content stores, so **no serialization exists in the hot diff --git a/docs/OGAR-CONSUMER-BEST-PRACTICES.md b/docs/OGAR-CONSUMER-BEST-PRACTICES.md new file mode 100644 index 0000000..f61a89d --- /dev/null +++ b/docs/OGAR-CONSUMER-BEST-PRACTICES.md @@ -0,0 +1,386 @@ +# OGAR Consumer Best Practices — the muscle-memory guide + +> **Audience:** every consumer-side session — medcare-rs, woa-rs, +> smb-office-rs, odoo-rs, openproject-nexgen-rs, q2, and any future +> consumer. +> +> **Why this doc:** the OGAR canon is right. The trap spellbook +> (`SURREAL-AST-TRAP-PREFLIGHT.md`) is right. But sessions don't get +> patterns from principles — they get them from **repeated worked +> examples**. This guide is the muscle-memory pass: every canonical +> pattern shown with a concrete classid, a real consumer, real code, +> and an anti-example alongside. +> +> Read once before authoring consumer code. Pattern-match against the +> examples; if your code doesn't look like one of them, stop and check. +> +> Status: **BEST PRACTICE v1** (2026-06-22). Append-only. +> +> Companions: `APP-CLASS-CODEBOOK-LAYOUT.md` (the layout spec), +> `CONSUMER-MIGRATION-HOWTO.md` (the migration recipe), +> `SURREAL-AST-TRAP-PREFLIGHT.md` (the trap to avoid), +> `CLASSID-RBAC-KEYSTONE-SPEC.md` (the authorize gate — pending probe). +> Parallel-session sibling: `lance-graph/.claude/knowledge/ogar-consumer-preflight.md`. + +--- + +## §0. The one-line invariant — read this before everything else + +> **The classid is pure address. The magic is what the address +> resolves to.** + +Both halves of the classid are address dimensions, never behavior: + +``` +classid : u32 = 0xAAAA ‖ 0xDDCC (8 nibbles) + │ │ + │ └─ lo u16: WHICH CONCEPT ─┐ + │ (DD = domain, │ + │ CC = concept index) │ BOTH halves + │ shared across all apps ├─ are pure + │ │ ADDRESS. + └─ hi u16: WHOSE RENDER ───────────────┤ Neither + (APP / ClassView / Askama template) │ carries + per-app; never shared ─┘ behavior. + + ──────► resolves to ──────► + │ + ├─ ClassView the SKIN (render shape, per-app — picked by hi) + ├─ Class the SHAPE (structural — canonical, picked by lo) + └─ ActionDef + the MAGIC (behavioral — lifecycle, callbacks, + KausalSpec validations; ALWAYS in OGAR Core, + NEVER in DDL or in the address) +``` + +**Three drilled corollaries:** + +1. **The address is dumb.** Knowing the classid tells you the *shape* + *skin* + which Core node to fetch — it tells you **nothing** about behavior. Behavior lives at the resolution target. +2. **Class-magic ≠ render-magic.** The hi u16 chooses **render** magic (which app's template), not **class** magic (which callbacks/lifecycle). Class magic is the Core's; render magic is the app's. +3. **You can't smuggle magic into the address.** Encoding behavior in DDL constructs (`DEFINE EVENT … WHEN … THEN …`) puts magic where only the address lives — the trap `SURREAL-AST-TRAP-PREFLIGHT.md` exists to prevent. + +--- + +## §1. Address anatomy — worked examples that drill the layout + +Memorize these. They appear across every consumer doc; recognizing them on sight is the muscle. + +``` +0x0000_0901 ─── canonical patient (core anchor, no app skin) + │ │ + │ └── 0x09 = Health domain · 0x01 = patient concept + └───────── 0x0000 = core (shared, no app prefix) + +0x0005_0901 ─── Medcare's patient — same concept, Medcare's render lens + ↑ ↑ + │ └── same 0x0901 = same RBAC grant, same ontology, same OGIT identity + └───────── 0x0005 = Medcare's APP prefix → Medcare's ClassView + Askama template + +0x0001_0102 ─── OpenProject's WorkPackage +0x0007_0102 ─── Redmine's Issue + ↑ ↑ + │ └── BOTH lo = 0x0102 = project_work_item + │ → ONE RBAC grant lattice (project_role 0x0117) + │ → ONE ontology shape + └───────────────── DIFFERENT hi u16 → DIFFERENT templates, zero concept dup + +0x0003_0103 ─── WoA's Stundenzettel (billable_work_entry) +0x0004_0103 ─── SMB's Stundenzettel +0x0001_0103 ─── OpenProject's TimeEntry (planner side) +0x0007_0103 ─── Redmine's TimeEntry (planner side) +0x0002_0103 ─── Odoo's HrAttendance / account.move.line(qty=hours) + ↑ ↑ + │ └── ALL share lo = 0x0103 = BILLABLE_WORK_ENTRY + │ → ONE canonical billable-time concept + │ → the cross-fork convergence pin (OGAR #93/#94/#96) + └───────────────── FIVE app-private renders of the same concept + "Planner times align with billable hours" = + a codebook lookup, not a translation layer. +``` + +**APP prefix allocation** (committed; OGAR #95 §2): + +| APP `0xAAAA` | App | Consumer crate | +|---|---|---| +| `0x0000` | shared core / no app skin | — | +| `0x0001` | OpenProject | openproject-nexgen-rs | +| `0x0002` | Odoo | odoo-rs | +| `0x0003` | WoA | woa-rs | +| `0x0004` | SMB | smb-office-rs | +| `0x0005` | Medcare / Healthcare | medcare-rs | +| `0x0007` | Redmine | (consumer TBD) | + +**Domain bytes** (committed; the high byte of the lo u16): + +| `0xDD` | Domain | +|---|---| +| `0x01` | project mgmt | +| `0x02` | commerce / ERP | +| `0x07` | OSINT | +| `0x09` | Health | + +--- + +## §2. The four canonical consumer patterns + +Every consumer code path is one of these four. Pattern-match before you write. + +### Pattern 1 — pull a classid (the codebook lookup) + +The canonical concept ID, from the consumer's PortSpec, via static +function call. **No registry, no hydration, no bridge.** + +```rust +// ✅ CANONICAL — direct PortSpec lookup +use ogar_vocab::ports::{HealthcarePort, PortSpec}; + +let cid: Option = HealthcarePort::class_id("Patient"); +// → Some(0x0901) +``` + +| Consumer | Port | Example call | Returns | +|---|---|---|---| +| medcare-rs | `HealthcarePort` | `::class_id("Patient")` | `Some(0x0901)` | +| medcare-rs | `HealthcarePort` | `::class_id("Befund")` (Dx alias) | `Some(0x0902)` | +| woa-rs | `WoaPort` | `::class_id("Stundenzettel")` | `Some(0x0103)` | +| woa-rs | `WoaPort` | `::class_id("TimeEntry")` (EN alias) | `Some(0x0103)` | +| smb-office-rs | `SmbPort` | `::class_id("Kunde")` | `Some(0x0204)` (BILLING_PARTY) | +| odoo-rs | `OdooPort` | `::class_id("res.partner")` | `Some(0x0204)` | +| odoo-rs | `OdooPort` | `::class_id("hr.attendance")` | `Some(0x0103)` | +| openproject-nexgen-rs | `OpenProjectPort` | `::class_id("WorkPackage")` | `Some(0x0102)` | +| (any Redmine consumer) | `RedminePort` | `::class_id("Issue")` | `Some(0x0102)` | + +```rust +// ❌ ANTI — go through the deprecated bridge layer +use lance_graph_ogar::bridges::HealthcarePort; // ← works, but extra hop; + // migrate to ogar_vocab::ports + +// ❌ ANTI — re-mint the canonical id locally +const PATIENT_CLASSID: u16 = 0x0901; // ← bypasses PortSpec; + // loses the alias-table mapping + +// ❌ ANTI — construct a UnifiedBridge to ask the same question +let b = MedcareBridge::new(registry)?; // ← deprecated alias; round-trip via +let ent = b.entity("Patient")?; // bridge + registry just to recover +let cid = ent.schema_ptr.entity_type_id(); // what PortSpec gives in one call +``` + +### Pattern 2 — compose a render classid (concept ‖ APP prefix) + +Once you have the concept's low u16 and want the **per-app render +address** (for ClassView dispatch, template selection, SoA row layout), +stamp the app's `APP_PREFIX` via OGAR #97's typed helper. + +```rust +// ✅ CANONICAL — typed APP_PREFIX from the PortSpec +use ogar_vocab::ports::{HealthcarePort, PortSpec}; + +let cid: u16 = HealthcarePort::class_id("Patient").unwrap(); // 0x0901 +let render_classid: u32 = HealthcarePort::APP_PREFIX | (cid as u32); +// 0x0005_0000 | 0x0901 +// = 0x0005_0901 ← Medcare's patient render address + +// → resolves to: +// ClassView = Medcare's clinical patient view (Askama template: +// patient.html, PII leaf-rename adapter, German labels +// stripped at the membrane) +// Class = canonical Patient (OGAR Core, shared with every health app) +// ActionDef/ = canonical Healthcare lifecycle (NOT app-private — +// KausalSpec in Core, behavior shared) +``` + +Worked examples by app: + +```rust +HealthcarePort::APP_PREFIX | 0x0901 → 0x0005_0901 // Medcare patient +WoaPort::APP_PREFIX | 0x0103 → 0x0003_0103 // WoA Stundenzettel +SmbPort::APP_PREFIX | 0x0204 → 0x0004_0204 // SMB Kunde +OdooPort::APP_PREFIX | 0x0103 → 0x0002_0103 // Odoo HrAttendance +OpenProjectPort::APP_PREFIX | 0x0102 → 0x0001_0102 // OpenProject WorkPackage +RedminePort::APP_PREFIX | 0x0102 → 0x0007_0102 // Redmine Issue +``` + +```rust +// ❌ ANTI — hardcode the APP prefix as a magic constant +const MEDCARE_APP: u32 = 0x0005_0000; // ← drifts from PortSpec +let render = MEDCARE_APP | (cid as u32); // if APP allocation changes + +// ❌ ANTI — bit-shift inline +let render = ((0x0005u32) << 16) | (cid as u32); // ← un-typed; lose source-of-truth + +// ❌ ANTI — store full u32 render classid where lo u16 would do (RBAC, ontology) +fn authorize(actor: &Actor, render_cid: u32, op: Op) { … } +// ^^^^^^^^^^^^ shared grant lattice keys on LO u16; +// passing the full u32 leaks render lens +// into auth (concept is shared, render is not) +``` + +### Pattern 3 — authorize by classid (PENDING the keystone) + +The keystone `authorize(actor, classid, op)` is **[H]** and gated on +`PROBE-OGAR-RBAC-AUTHORIZE`. Until it ships, **keep your existing auth** +— do NOT re-introduce a bridge as a stopgap. + +```rust +// ⏳ FUTURE — once lance-graph-rbac keystone ships: +use lance_graph_rbac::authorize; + +let concept: u16 = HealthcarePort::class_id("Patient").unwrap(); // 0x0901 +let decision = authorize(&actor, concept, Op::Read); +// ^^^^^^^ KEY ON LO u16: shared grant lattice +// across all health apps +``` + +```rust +// ✅ INTERIM — keep existing static_role / Policy / membrane gate +fn authorize_patient_read(actor_role: &str) -> AccessDecision { + let role_static = static_role(actor_role); + // medcare-rbac::Policy check, OR MedCareMembraneGate, OR static role map + // — whatever your repo already uses + … +} +``` + +```rust +// ❌ ANTI — re-introduce a UnifiedBridge to "auth gate" while waiting for keystone +let bridge = MedcareBridge::new(registry)?; // ← deprecated; replaces one +let decision = bridge.authorize_read("Patient", &role); // trap with another. Wait + // for the real keystone. +``` + +### Pattern 4 — distill/migrate from a legacy bridge + +The migration recipe in two shapes: import-only repoint (cheap) vs +structural drop (real spell). Pick by what your code actually uses. + +**Sub-pattern 4a — import-only repoint** (lowest blast radius): + +```rust +// BEFORE: +use lance_graph_ogar::bridges::HealthcarePort; + ^^^^^^^ deprecated re-export path; lights up the beacon + +// AFTER (canonical): +use ogar_vocab::ports::HealthcarePort; +// ^^^^^^^^^^^^^^^^^ direct from the typed PortSpec source +``` + +Same symbol; cleaner import path; no behavior change. This is the +**one-line spell**. + +**Sub-pattern 4b — structural drop** (the full migration): + +```rust +// BEFORE — UnifiedBridge wraps the lookup: +use lance_graph_callcenter::UnifiedBridge; +use lance_graph_ogar::bridges::MedcareBridge; // deprecated alias +let bridge = UnifiedBridge::::new(…); +let cid = bridge.entity("Patient")?.schema_ptr.entity_type_id(); + +// AFTER — direct PortSpec call, no bridge: +use ogar_vocab::ports::{HealthcarePort, PortSpec}; +let cid = HealthcarePort::class_id("Patient").unwrap(); // same value, no wrapper +``` + +If your consumer has a `per-consumer-bridge` crate (`medcare-bridge`, +`smb-bridge`, etc.) — per `CONSUMER-MIGRATION-HOWTO.md` it gets +**deleted** at the end of the migration. The consumer holds **no** +ontology. + +--- + +## §3. Anti-pattern catalogue — drill these alongside the right shapes + +Each anti-pattern is paired with its right shape above. Memorizing the +shapes negatively is half the muscle memory. + +| Anti-pattern | Right shape (§reference) | Why it bites | +|---|---|---| +| Re-mint a canonical classid locally (`const PATIENT = 0x0901`) | §2 Pattern 1 | Bypasses PortSpec — loses alias table, alias-to-id stability is a per-Port guarantee | +| Hardcode `APP_PREFIX` as a literal (`0x0005_0000`) | §2 Pattern 2 | Drifts from the typed source; APP allocation changes break silently | +| Pass full u32 render classid to authorize() | §2 Pattern 3 (note) | Auth keys on LO u16; passing full u32 leaks render lens into grant lookup | +| Use the deprecated `lance_graph_ogar::bridges::*` re-export | §2 Pattern 4a | The beacon will warn (lance-graph #589/#590); canonical path is `ogar_vocab::ports` | +| Re-introduce `UnifiedBridge` as a stopgap while keystone is pending | §2 Pattern 3 (anti) | Replaces one deprecated wrapper with the same wrapper. Wait for the real authorize | +| Smuggle behavior into DDL via `DEFINE EVENT … WHEN … THEN …` | `SURREAL-AST-TRAP-PREFLIGHT.md` | Behavior belongs in OGAR Core (`ActionDef` + `KausalSpec`), not in the address-side artifact | +| Mint a per-tenant low-u16 to dodge sharing the canonical class | §0 corollary 1 | The point of the lo u16 is sharing; per-tenant variation lives in the HI u16 (render lens) | + +--- + +## §4. Worked migration narrative — Medcare's patient end-to-end + +To cement: the same patient concept seen through every pattern. + +```rust +// The address — pure routing identity +let concept_cid: u16 = HealthcarePort::class_id("Patient").unwrap(); // 0x0901 +let render_cid: u32 = HealthcarePort::APP_PREFIX | (concept_cid as u32); +// = 0x0005_0901 + +// The address resolves to (no magic on the address itself): +// +// render_cid (0x0005_0901) +// │ +// ├──► ClassView::resolve(0x0005_0901) → Medcare's patient view +// │ (template: patient.html, +// │ PII leaf-rename at adapter, +// │ German labels stripped at membrane) +// │ +// ├──► Class::canonical(0x0901) → Patient (OGAR Core, shared) +// │ (attributes: name, dob, mrn, …; +// │ associations: diagnoses[], visits[]) +// │ +// └──► ActionDef::for_class(0x0901) → [validate_mrn_unique, +// before_save_audit, +// after_destroy_archive, …] +// (canonical Healthcare lifecycle — +// ALL apps share these, hi u16 doesn't matter) + +// Pattern 1: pull the classid +let cid = HealthcarePort::class_id("Patient").unwrap(); + +// Pattern 2: compose the render +let render = HealthcarePort::APP_PREFIX | (cid as u32); + +// Pattern 3: authorize (interim — keystone pending) +let decision = static_role_check(actor, "physician"); // existing medcare-rbac path +// ^^^^^^^^^^^^^^^^^ NOT a UnifiedBridge — that's the anti-pattern + +// Pattern 4: render — emit the view through the right template +let html = render_classview(render_cid, &patient_row)?; // ClassView dispatch on full u32 +``` + +This is the whole shape of a clean consumer call site. Every consumer +that resolves a Patient (or a WorkPackage, or a Stundenzettel) does +the same four-step dance with different ids. + +--- + +## §5. When this doc fires (Knowledge Activation) + +**Trigger phrases** — if your prompt contains any of these, this doc +is mandatory pre-read: + +`classid` · `class_id(` · `APP_PREFIX` · `PortSpec` · `HealthcarePort` · +`WoaPort` · `SmbPort` · `OdooPort` · `OpenProjectPort` · `RedminePort` · +`ClassView` · `MedcareBridge` · `WoaBridge` · `SmbBridge` · `OdooBridge` · +`OpenProjectBridge` · `RedmineBridge` · `UnifiedBridge` · `ogar_vocab::ports` · +`lance_graph_ogar::bridges` · `render classid` · `concept classid` · +`per-consumer bridge` · `consumer migration` + +**Agents that load it Tier-1**: +- `core-first-architect` · `adapter-shaper` · `core-gap-auditor` +- The `Plan` subagent on any consumer-side task +- Any per-consumer agent specifically (medcare-bridge specialist, + woa-rs-rust transcoder, etc.) + +## §6. Cross-references + +- `docs/APP-CLASS-CODEBOOK-LAYOUT.md` — the formal layout spec (with §3.5–3.7 render/RAG/Firewall logic) +- `docs/CONSUMER-MIGRATION-HOWTO.md` — the per-consumer migration recipe +- `docs/SURREAL-AST-TRAP-PREFLIGHT.md` — the trap to avoid (don't smuggle magic into address-side DDL) +- `docs/CLASSID-RBAC-KEYSTONE-SPEC.md` — the pending `authorize(actor, classid, op)` keystone +- `docs/OGAR-AST-CONTRACT.md` — the canonical Class / ActionDef IR (what the address resolves to) +- `docs/APP-CODEBOOK-MIGRATION-PLAN.md` — the wave-ordered consumer migration plan (W0–W4) +- **Parallel-session sibling**: `lance-graph/.claude/knowledge/ogar-consumer-preflight.md` + (lance-graph #591) — the spine-side spellbook with the + `ISS-CONTRACT-APP-PREFIX-MIRROR` Core-gap entry; pin the same + address-vs-magic precision diff --git a/docs/SURREAL-AST-TRAP-PREFLIGHT.md b/docs/SURREAL-AST-TRAP-PREFLIGHT.md index 0d8cf7f..037700d 100644 --- a/docs/SURREAL-AST-TRAP-PREFLIGHT.md +++ b/docs/SURREAL-AST-TRAP-PREFLIGHT.md @@ -23,6 +23,44 @@ > (`core-first-architect`, `adapter-shaper`, `core-gap-auditor`, > the `Plan` subagent) loads this Tier-1 before output. +## §0. Why this trap is structurally impossible to fix from inside DDL + +> **The classid is pure address. The magic is what the address +> resolves to.** (Drilled with worked examples in +> `OGAR-CONSUMER-BEST-PRACTICES.md` §0.) + +The classid (`0xAAAA_DDCC`) is **pure address**. Both halves — +hi u16 (APP / render lens) and lo u16 (canonical concept) — are +*address dimensions*. Neither carries behavior. + +Where does the behavior live? At the **resolution target**: + +``` +classid ──┬──► ClassView the SKIN (render — per-app) + ├──► Class (structural) the SHAPE (canonical — shared) + └──► ActionDef + KausalSpec the MAGIC (behavioral — lifecycle/ + callbacks/validations, + ALWAYS in OGAR Core) +``` + +This is the Core-First identity = classid rule: **the address is dumb, +the magic is what it points at**. Class-magic (callbacks, lifecycle, +validations — the rails ActiveRecord blob) is a property of the Core +node the address resolves to, **never** a property of the address itself. + +This is *why* the SurrealQL trap is structurally impossible from inside +the DDL: SurrealQL has the address but **not** the Core types it +resolves to. So encoding lifecycle in `DEFINE EVENT … WHEN … THEN …` +or sentinel comments is trying to **put the value into the key** — to +forge behavior at the wrong layer entirely. It looks like it works in +the narrow case and rots immediately at every other layer. + +The five questions below are the operational mirror that catches this +before keyboard. If you find yourself reaching for DDL to express +something the address can't carry, the question isn't *"how do I +encode this in DDL?"* — it's *"which Core type does this address +resolve to, and have I lifted the behavior into THAT?"* + ## The trap, in one paragraph You're reading an AR-shaped producer — Ruby callbacks, Odoo @@ -211,6 +249,11 @@ If the prompt mentions ANY of these, this doc is mandatory pre-read. ## Cross-refs +- `docs/OGAR-CONSUMER-BEST-PRACTICES.md` — the muscle-memory guide: + classid-address-vs-class-magic with worked examples across every + consumer (medcare, woa, smb, odoo, openproject, redmine); the four + canonical patterns; anti-pattern catalogue. Read this if you're + working on a consumer-side path. - `docs/SURREAL-AST-AS-ADAPTER.md` — the design decision (the *why*) - `docs/OGAR-AST-CONTRACT.md` — the canonical IR (the *what*) - `docs/THE-FIREWALL.md` — the structural reason DDL can't be the spine @@ -220,3 +263,7 @@ If the prompt mentions ANY of these, this doc is mandatory pre-read. that has no DDL home - `docs/APP-CODEBOOK-MIGRATION-PLAN.md` W3 — the worked remediation case (odoo-rs) +- **Parallel-session sibling**: `lance-graph/.claude/knowledge/ogar-consumer-preflight.md` + (lance-graph #591) — same spellbook pattern cast in lance-graph's + consumer domain; ships the `ISS-CONTRACT-APP-PREFIX-MIRROR` Core-gap + entry.