From 9ed4eb9a25b80a8ce3400dc1c55c2d00d1988369 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 15:56:38 +0000 Subject: [PATCH 01/22] chore(att): activate .claude/ATT/ NLSpecs in lance-graph plumbing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the three Attractor-style NLSpecs (autoattended-orchestrator, anti-skim-agent, agent-coordination-mcp) into this repo's bootload and governance surfaces so the spec actually governs agent behavior, not just exists in the tree. - .claude/settings.json: add Edit/Write/MultiEdit deny rules for the 8 append-only bookkeeping files. Closes coordination-mcp-spec §7.2 / §9 DoD ("append-only governance enforced at the settings layer") and brings actual enforcement in line with what BOOT.md already claims is enforced. - .claude/agents/BOOT.md: new "ATT NLSpec Coupling" section maps the four quality-lifecycle agents (convergence-architect, preflight- drift-auditor, baton-handoff-auditor, brutally-honest-tester) to ATT slots PP-14/16/15/13 with non-overlapping verdict vocabularies. - .claude/BOOT.md: "Existing content" section now points at both .claude/EN/ (operator cheat-sheet) and .claude/ATT/ (engineering spec) and names the three NLSpecs. - .claude/hooks/session-start.sh: SessionStart additionalContext surfaces the ATT pointer + ACTIVATION receipt so new sessions see them on turn 1. - .claude/ATT/ACTIVATION.md (new): honest DoD audit per spec. Records which items are satisfied (e.g., append-only governance, 4-savant non-overlap, handover schema), which are partial (DOT plans, BAP/PD enumerations), and which are gaps (status.json + proof-of-read schema, sentinel tokens, tool-call loop detection). No code under crates/ touched. Single-repo activation per user direction; other repos in the 26-repo rollout activate via their own branches. --- .claude/ATT/ACTIVATION.md | 115 +++++++++++++++++++++++++++++++++ .claude/BOOT.md | 13 ++++ .claude/agents/BOOT.md | 37 +++++++++++ .claude/hooks/session-start.sh | 8 +++ .claude/settings.json | 24 +++++++ 5 files changed, 197 insertions(+) create mode 100644 .claude/ATT/ACTIVATION.md diff --git a/.claude/ATT/ACTIVATION.md b/.claude/ATT/ACTIVATION.md new file mode 100644 index 00000000..ed9c28c5 --- /dev/null +++ b/.claude/ATT/ACTIVATION.md @@ -0,0 +1,115 @@ +# `.claude/ATT/` — Activation Receipt for `lance-graph` + +> **Activated:** 2026-05-21 on branch `claude/activate-lance-graph-att-k2pHI` +> **Scope:** Single-repo activation (lance-graph only). Other repos in +> the 26-repo rollout activate via their own branches. + +## What "activated" means in this repo + +The three NLSpecs at `.claude/ATT/*.md` (`autoattended-orchestrator-spec.md`, +`anti-skim-agent-spec.md`, `agent-coordination-mcp-spec.md`) were +imported as the engineering specification governing this repo's +`.claude/agents/` ensemble. Activation wires the spec into four +workspace plumbing surfaces: + +| Surface | Change | Purpose | +|---|---|---| +| `.claude/settings.json` | Added Edit/Write/MultiEdit deny rules for the 8 append-only bookkeeping files (`PR_ARC_INVENTORY.md`, `LATEST_STATE.md`, `STATUS_BOARD.md`, `INTEGRATION_PLANS.md`, `EPIPHANIES.md`, `ISSUES.md`, `IDEAS.md`, `TECH_DEBT.md`). | Closes [coordination-mcp-spec §7.2 / §9 DoD](./agent-coordination-mcp-spec.md#72-enforcement) — append-only governance now enforced at the settings layer, matching what `.claude/BOOT.md` already claimed was enforced. | +| `.claude/agents/BOOT.md` | New "ATT NLSpec Coupling" section near the top maps the four quality-lifecycle agents (`convergence-architect`, `preflight-drift-auditor`, `baton-handoff-auditor`, `brutally-honest-tester`) to ATT slots PP-14, PP-16, PP-15, PP-13 with non-overlapping verdict vocabularies. | Documents the operator ↔ engineering mapping. The agents already existed; this records the slot they occupy in the ATT taxonomy. | +| `.claude/BOOT.md` | "Existing content" section now points at both `.claude/EN/` (operator) and `.claude/ATT/` (engineering), naming the three NLSpecs. | New sessions learn the spec exists during the mandatory bootload. | +| `.claude/hooks/session-start.sh` | Added an "Engineering spec" block to the `additionalContext` injected at SessionStart. | New sessions see the ATT pointer as a system reminder on turn 1. | + +No code under `crates/` was touched. Activation is documentation / +plumbing only. + +## Definition-of-Done audit (per spec) + +The three ATT specs each ship a DoD checklist. The status below is +honest as of the activation date — claims marked SATISFIED have a +named home in this repo; PARTIAL items have some coverage with a +known gap; GAP items have no current implementation and are filed +for follow-up work. + +### `autoattended-orchestrator-spec.md` §10 + +| DoD item | Status | Lives at | +|---|---|---| +| Sprint plans are DOT graphs or §6.4-YAML mirrors with required attrs | PARTIAL | `.claude/board/sprint-log-{10..13}/` carry YAML-ish plans; not all required node/edge attrs are present. Gap for new sprints. | +| Validation rules §7 WAVE-001..WAVE-015 run via `preflight-drift-auditor` and block on ERROR | PARTIAL | `preflight-drift-auditor` exists (`.claude/agents/preflight-drift-auditor.md`) and `.claude/tools/preflight_drift.rs` exists; the WAVE-NNN rule IDs are not yet a one-to-one match. | +| Every worker spawns with `isolation: "worktree"` | SATISFIED | Agent tool default for spawned workers in current sessions. | +| Every worker writes a `status.json` matching §9.1 | GAP | Workers currently emit free-form completion text. `status.json` schema not enforced. | +| Missing `status.json` = FAIL (`auto_status=false`) | GAP | No status file is required today; this is the headline gap. | +| Four savants present with non-overlapping verdict vocabularies + non-use route-tables | SATISFIED | Mapping documented in `.claude/agents/BOOT.md § ATT NLSpec Coupling` (this activation). | +| PP-13 owns Rust Tier-1 toolchain | SATISFIED | `.claude/agents/brutally-honest-tester.md` is wired to `cargo clippy / cargo fmt / cargo test / cargo audit / cargo deny`. | +| PP-15 owns BAP1..BAP10 + 8 boundary classes | PARTIAL | `.claude/agents/baton-handoff-auditor.md` exists; explicit BAP1..BAP10 enumeration not in the card. | +| PP-16 owns PD1..PD10 + 6 axes + §7 validation | PARTIAL | `.claude/agents/preflight-drift-auditor.md` + `.claude/tools/preflight_drift.rs`; PD-IDs not yet enumerated. | +| Worker briefs declare `proof_of_read: true` + unique `sentinel_token` | GAP | No worker template enforces sentinel tokens today. | +| Meta-agent drains REQUESTS-FROM-AGENTS.md ≥ 2×/day | GAP | No `META/REQUESTS-FROM-AGENTS.md` file; `.claude/board/AGENT_LOG.md` is the closest analogue. | +| PR review classifies findings ONLY as P0 or P1 | SATISFIED | Existing convention in `.claude/board/PR_ARC_INVENTORY.md`. | +| `INVARIANTS.md` ≤ 500 lines | N/A | No `INVARIANTS.md` in this repo (iron rules live in `CLAUDE.md`). | +| One consolidation commit per wave at shared registry; zero N-mini-commit anti-pattern | PARTIAL | Convention followed by recent sprints; not yet a validation rule. | +| Files > 150 lines written via `tee -a` (chunking discipline) | PARTIAL | BOOT.md mandates `tee -a` for the 8 bookkeeping files; not yet enforced for all > 150-line writes. | +| Context Fidelity ladder §11A with precedence edge > node > graph > default | GAP | Context fidelity is not currently configured. | +| `fidelity=truncate` does NOT exempt the §3.3 reading-depth ladder | N/A | Depends on the fidelity ladder being live; see above. | + +### `anti-skim-agent-spec.md` §10 + +| DoD item | Status | Lives at | +|---|---|---| +| Every worker brief contains a unique `sentinel_token` | GAP | Not enforced. | +| Worker first-reply begins with sentinel verbatim | GAP | Not enforced. | +| Workers emit `status.json` with proof-of-read entries per file | GAP | See orchestrator §9.1 gap above. | +| Proof-of-read entries declare `sha256` + `lines` + depth | GAP | No structured proof-of-read today. | +| Workers run §6.1 tool-call loop detector after every tool call (N=10) | GAP | Tool-call loop detection is not implemented; AP9 is described in the spec but not detected automatically. | +| Stuck workers use one of five §5.1 blocker types | GAP | No structured blocker schema today. | +| Meta-agent spot-checks one of LD-1..5 per savant per wave | GAP | No rotating spot-check protocol today. | +| Drift signals §4.3 scanned before goal-gate verdict | PARTIAL | `preflight-drift-auditor` runs board-vs-Cargo drift; LD-style drift signals not a separate scan. | +| PP-13 runs Tier-1 toolchain; Tier-1 failure → FAIL | SATISFIED | See orchestrator audit row above. | +| PP-13 anti-pattern scan covers AP1..AP9 | PARTIAL | `brutally-honest-tester.md` covers codex-class bugs; not all 9 APs are enumerated. | +| Tool-output truncation surfaces in proof-of-read as `truncated:head-N` / `truncated:tail-N` | GAP | Proof-of-read not yet structured. | +| `auto_status=false` mandatory; missing `status.json` = FAIL | GAP | See orchestrator §9.1 gap above. | + +### `agent-coordination-mcp-spec.md` §9 + +| DoD item | Status | Lives at | +|---|---|---| +| Three layers (Teleport / Blackboard / Branch Pub/Sub) implementable with existing primitives | SATISFIED | `.claude/agents/*.md` (Layer-0), `.claude/board/AGENT_LOG.md` (Layer-1), `mcp__github__subscribe_pr_activity` (Layer-2). | +| Native MCP endpoints §5.1–§5.4 OR documented workaround | SATISFIED | Workaround mode (file blackboard + GitHub MCP) is the documented fallback. | +| `BlackboardEntry`, `ProofOfRead`, `Handover`, `RequestEntry`, `AnswerEntry` schemas implemented + validated | PARTIAL | `Handover` schema present (`.claude/agents/BOOT.md § Handover Protocol`); other four are described in the ATT spec but not implemented as validators in this repo. | +| Append-only governance §7 enforced at `.claude/settings.json` for the 8 bookkeeping files | **SATISFIED** | **Closed by this activation** — see `.claude/settings.json` deny rules added in this commit. | +| Single-mutable-file invariant: `Stand.md` / `STATUS_BOARD.md` is the ONLY file workers may overwrite | PARTIAL | `STATUS_BOARD.md` is in the deny list now (immutable rows; only Status column is mutable per BOOT.md table). The "only file workers may overwrite" invariant is not yet enforced as a positive allowlist. | +| Decision matrix §4 followed: workers use right transport for right message | SATISFIED | Documented in `.claude/agents/BOOT.md § A2A Orchestration`. | +| Coordination PRs are draft, named `claude/`, marked `Do not merge` | SATISFIED | Branch convention followed; "Do not merge" body marker is per-PR. | +| Handover files use §6.3 schema with required sections | SATISFIED | `.claude/agents/BOOT.md § Handover Protocol` matches §6.3 schema. | + +## Headline gaps for follow-up + +The activation closes one DoD item outright (append-only governance +at settings layer) and documents the operator ↔ engineering mapping +for the four quality-lifecycle savants. The structural gaps that +remain — and are filed for separate PRs, not this activation: + +1. **`status.json` + proof-of-read schema** (orchestrator §9.1, + anti-skim §7) — workers do not yet emit structured status with + per-file `sha256` + `lines` + depth. This is the largest single + gap and the headline conformance item. +2. **Sentinel-token / Lie-Detector LD-1..5 enforcement** (anti-skim + §4) — no spot-checks today. +3. **Tool-call loop detection N=10** (anti-skim §6) — not + implemented. +4. **Typed blocker schema for stuck workers** (anti-skim §5) — no + schema today. +5. **DOT-graph sprint plans with WAVE-001..WAVE-015 validation** + (orchestrator §6–§7) — current YAML-ish plans don't carry all + required node/edge attrs. + +Each gap should be addressed via a dedicated PR rather than bundled +into this activation, so the "spec is wired, code-level gaps are +visible" property of this receipt remains intact. + +## Provenance + +- Source spec: `.claude/ATT/README.md` (this directory) +- EN sibling: `.claude/EN/README.md` +- Format inspiration: [strongdm/attractor](https://github.com/strongdm/attractor) +- Activation branch: `claude/activate-lance-graph-att-k2pHI` diff --git a/.claude/BOOT.md b/.claude/BOOT.md index cd342f55..3556b602 100644 --- a/.claude/BOOT.md +++ b/.claude/BOOT.md @@ -193,6 +193,19 @@ should reference these, not recreate them: - **`.claude/hooks/*.sh`** — SessionStart and PostCompact hooks wired via `.claude/settings.json`. - **`.claude/skills/cca2a/`** — the A2A pattern explanation skill. +- **`.claude/EN/`** — project-agnostic multi-agent kit (operator + cheat-sheet, in-session use). See `.claude/EN/README.md`. +- **`.claude/ATT/`** — Attractor-style NLSpec restatement of the + same kit (engineering spec, build-time use). Three specs: + `autoattended-orchestrator-spec.md` (wave loop, 4-savant slots, + worker iron rules, validation WAVE-001..WAVE-017), + `anti-skim-agent-spec.md` (Reading-Depth Ladder, Lie-Detector + LD-1..5, stuck-protocol blockers, AP1..AP9), and + `agent-coordination-mcp-spec.md` (Layer-0/1/2 coordination, + handover schema, append-only governance §7.2). DoD checklists + + Cross-Language/Provider Parity Matrix at the end of each spec. + Activation receipt: `.claude/ATT/ACTIVATION.md`. See + `.claude/ATT/README.md` for the EN↔ATT relationship. Before creating a new `.claude/*.md` file, grep the existing 61 docs and 41 prompts for the topic. Most architectural concerns have diff --git a/.claude/agents/BOOT.md b/.claude/agents/BOOT.md index 04abf9af..0f52bcd2 100644 --- a/.claude/agents/BOOT.md +++ b/.claude/agents/BOOT.md @@ -5,6 +5,43 @@ This folder contains focused agent cards for `lance-graph`. The goal is not to multiply personalities for decoration. The goal is to keep unlike architectural concerns from being flattened into one fuzzy voice. +## ATT NLSpec Coupling (engineering spec for this ensemble) + +This file is the **operator's** view of the ensemble (which agent to wake, +what trigger fires it, what knowledge it loads). The **engineering** view +lives at `.claude/ATT/`, in three Attractor-style NLSpecs: + +| ATT spec | Governs | Anchors in this ensemble | +|---|---|---| +| [`.claude/ATT/autoattended-orchestrator-spec.md`](../ATT/autoattended-orchestrator-spec.md) | Wave-loop, 4-savant verdict gates, worker iron rules, DOT-graph plan format, `status.json` schema (`auto_status=false`), validation rules WAVE-001..WAVE-017. | The 4 savants (PP-13/14/15/16) below ARE the four slot-occupants. Quality-lifecycle rows in the Knowledge Activation table route work into the corresponding slot. | +| [`.claude/ATT/anti-skim-agent-spec.md`](../ATT/anti-skim-agent-spec.md) | Reading-Depth Ladder (grep→read→thorough→fan-out), Lie-Detector LD-1..5 (sentinel / proof-of-read SHA / 3-section challenge / negative-knowledge / line-range quote), typed stuck-protocol blockers, tool-call loop detection, Tier-1/2/3 toolchains, AP1..AP9 anti-patterns. | Every worker spawned by any agent in this ensemble is subject to LD-1..5. PP-13 (`brutally-honest-tester`) owns the §8.1 Tier-1 toolchain for Rust. The Handover Protocol below is the proof-of-read carrier. | +| [`.claude/ATT/agent-coordination-mcp-spec.md`](../ATT/agent-coordination-mcp-spec.md) | Three coordination layers (Layer-0 teleportation, Layer-1 file blackboard, Layer-2 branch pub/sub), structured handover schema, decision matrix, `BlackboardEntry`/`ProofOfRead`/`Handover`/`RequestEntry`/`AnswerEntry` schemas, append-only governance §7. | `.claude/board/AGENT_LOG.md` IS the Layer-1 blackboard. `.claude/handovers/*.md` (§ Handover Protocol below) are the Layer-3 structured handovers. The 8 append-only bookkeeping files are enforced via `.claude/settings.json` deny rules (§7.2 enforcement). | + +**Quality-lifecycle slot mapping** (the bottom block of the Knowledge +Activation Protocol table below maps each lifecycle phase to one savant; +the ATT spec calls these PP-13/14/15/16): + +| Lifecycle phase | This ensemble (agent card) | ATT slot | Verdict vocabulary (non-overlapping per §4.2) | +|---|---|---|---| +| PRE-PLAN — divergent expansion | `convergence-architect` | PP-14 | EPIPHANY / INVARIANT-CANDIDATE / WITHDRAW | +| PRE-SPAWN — preflight drift | `preflight-drift-auditor` | PP-16 | DRIFT-BLOCK / DRIFT-WARN / DRIFT-CLEAR | +| DURING-IMPL — boundary handoff | `baton-handoff-auditor` | PP-15 | BAP-HIT / BAP-CLEAR / OUT-OF-SCOPE | +| POST-IMPL — within-crate bug hunt | `brutally-honest-tester` | PP-13 | FAIL / PASS / WAIVED | + +**Why both EN and ATT?** `.claude/EN/` is the project-agnostic operator +cheat-sheet (prose, in-session use); `.claude/ATT/` is the same kit +restated as NLSpec with DoD checklists, validation rules, and a +cross-language parity matrix (build-time use). Either can stand alone; +together they let a coding agent both *follow* the pattern in-session +and *implement / audit* it across the 26-repo rollout. See +[`.claude/ATT/README.md`](../ATT/README.md) for the two-document split +rationale and the five wins adopted from `strongdm/attractor`. + +**ATT activation receipt** for this repo: see +[`.claude/ATT/ACTIVATION.md`](../ATT/ACTIVATION.md) — records which +Definition-of-Done items are currently satisfied by this ensemble and +which are explicit gaps. + ## Existing agents ### `container-architect` diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index 3f3458f3..c1d9573f 100755 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -51,6 +51,14 @@ Do NOT propose any new type, module, or convention without grepping LATEST_STATE .claude/skills/cca2a/SKILL.md — read once to grok the pattern, skip re-deriving. +## Engineering spec (when implementing or auditing the agent kit) + +.claude/ATT/ — Attractor-style NLSpecs (DoD checklists + Cross-Language/Provider parity matrices): + - autoattended-orchestrator-spec.md (wave loop, 4-savant slots PP-13/14/15/16, worker iron rules, validation WAVE-001..WAVE-017) + - anti-skim-agent-spec.md (Reading-Depth Ladder, Lie-Detector LD-1..5, stuck-protocol blockers, AP1..AP9 anti-patterns) + - agent-coordination-mcp-spec.md (Layer-0/1/2 coordination, handover schema, append-only governance §7.2) +.claude/ATT/ACTIVATION.md — receipt: which DoD items this repo currently satisfies + which are explicit gaps. + ## Full spec CLAUDE.md at workspace root.' diff --git a/.claude/settings.json b/.claude/settings.json index a0537cac..4f64745a 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -149,6 +149,30 @@ "Edit(**/CLAUDE.md)", "Write(CLAUDE.md)", "Write(**/CLAUDE.md)", + "Edit(.claude/board/PR_ARC_INVENTORY.md)", + "Edit(.claude/board/LATEST_STATE.md)", + "Edit(.claude/board/STATUS_BOARD.md)", + "Edit(.claude/board/INTEGRATION_PLANS.md)", + "Edit(.claude/board/EPIPHANIES.md)", + "Edit(.claude/board/ISSUES.md)", + "Edit(.claude/board/IDEAS.md)", + "Edit(.claude/board/TECH_DEBT.md)", + "Write(.claude/board/PR_ARC_INVENTORY.md)", + "Write(.claude/board/LATEST_STATE.md)", + "Write(.claude/board/STATUS_BOARD.md)", + "Write(.claude/board/INTEGRATION_PLANS.md)", + "Write(.claude/board/EPIPHANIES.md)", + "Write(.claude/board/ISSUES.md)", + "Write(.claude/board/IDEAS.md)", + "Write(.claude/board/TECH_DEBT.md)", + "MultiEdit(.claude/board/PR_ARC_INVENTORY.md)", + "MultiEdit(.claude/board/LATEST_STATE.md)", + "MultiEdit(.claude/board/STATUS_BOARD.md)", + "MultiEdit(.claude/board/INTEGRATION_PLANS.md)", + "MultiEdit(.claude/board/EPIPHANIES.md)", + "MultiEdit(.claude/board/ISSUES.md)", + "MultiEdit(.claude/board/IDEAS.md)", + "MultiEdit(.claude/board/TECH_DEBT.md)", "Bash(git push --force:*)", "Bash(git push -f:*)", "Bash(git push --force-with-lease:*)", From a00ab6501bf9f3f86d9c270a9312a6fc8e10a8db Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 16:25:58 +0000 Subject: [PATCH 02/22] =?UTF-8?q?docs(plans):=20add=204=20integration=20pl?= =?UTF-8?q?ans=20=E2=80=94=20UnifiedBridge=20consumer=20migration=20+=20pe?= =?UTF-8?q?r-repo=20lance-graph=20wiring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drafted four planning documents under .claude/plans/, indexed at the end of .claude/board/INTEGRATION_PLANS.md per the workspace's APPEND-ONLY governance rule (tee -a, no Edit). - unified-bridge-consumer-migration-v1.md (257 lines): convergence of woa-bridge / smb-bridge / medcare-bridge onto a single constructor pattern returning lance_graph_callcenter::UnifiedBridge. CAM codebook + schema + label + verbs from OGIT + OWL/DOLCE materialised as OgitFamilyTable, persisted append-only in a LanceDB column under the lance-cache feature. D-UB-1..11 across 4 tiers; D-UB-7 (fix ontology_dto.rs:85 in MedCare-rs) is the critical path; D-UB-8 (RLS coverage for Treatment / Visit / VitalSign) is safety-critical. - lance-graph-in-woa-rs-v1.md (132 lines): greenfield 6-phase lift from zero (OGIT TTL + sea-orm/MySQL writer-parity) to ontology + RBAC + Lance-third-writer + optional Cypher/CAM-PQ surface. Phase 0 mechanical (vendor + exclude); Phase 1 lands woa-bridge + woa-ontology crates mirroring MedCare-rs and smb-office-rs; Phase 4-5 opt-in. Respects 2026-05-15 DualSink-Pivot. - lance-graph-in-smb-office-rs-v1.md (91 lines): 5-phase completion of what unified_bridge_wiring.rs's doc-comments already promise. Phase A ships dedicated SmbBridge upstream; Phase B authors OGIT/NTO/SMB/ TTL + SMB-shaped role groups (tax_clerk / partner / client_user / audit_observer) per D-SDR-2 + swaps smb_unified_bridge to UnifiedBridge; Phase C consolidates rich auth::TenantId with transparent callcenter::TenantId; Phases D-E opt-in. - lance-graph-in-medcare-rs-v1.md (129 lines): 7-phase completion of the post-#355 migration. Critical path on Phase 1 (fix the broken lance-phase2 build at ontology_dto.rs:85) + Phase 2 (close RLS fail-OPEN window — safety-critical for HIPAA conformance). Phase 5 unblocks LanceProbe M2..M6 via 5 medcare-rs endpoints (D-LGMC-7..11 = D-SDR-35..39 in super-domain plan). Two-branch reality respected: main vs claude/scaffold-medcare-rs-rZD5A. All four plans cross-reference super-domain-rbac-tenancy-v1.md as the substrate spec. No code touched; documentation only. --- .claude/board/INTEGRATION_PLANS.md | 50 ++++ .claude/plans/lance-graph-in-medcare-rs-v1.md | 129 +++++++++ .../plans/lance-graph-in-smb-office-rs-v1.md | 91 +++++++ .claude/plans/lance-graph-in-woa-rs-v1.md | 132 +++++++++ .../unified-bridge-consumer-migration-v1.md | 257 ++++++++++++++++++ 5 files changed, 659 insertions(+) create mode 100644 .claude/plans/lance-graph-in-medcare-rs-v1.md create mode 100644 .claude/plans/lance-graph-in-smb-office-rs-v1.md create mode 100644 .claude/plans/lance-graph-in-woa-rs-v1.md create mode 100644 .claude/plans/unified-bridge-consumer-migration-v1.md diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index b9d7c881..6207ecf1 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -618,3 +618,53 @@ This sprint was orchestrated as 12 worker agents + 1 meta agent on branch `claud - `.claude/board/LATEST_STATE.md` — sprint-3 entry (paired with this index update) - `.claude/board/TECH_DEBT.md` — TD-OGIT-G-SLOT-1 through TD-DEEPNSM-NSM-COLLAPSE-11 (the 11 entries spec'd by sprint-3) - `.claude/board/sprint-log-3/` — coordination directory (12 agent logs + meta-1 + sprint-summary) + +## 2026-05-21 — unified-bridge-consumer-migration-v1 (UnifiedBridge wiring across woa-rs / smb-office-rs / MedCare-rs) + +**Status:** Active (DRAFT — sister to super-domain-rbac-tenancy-v1 §3.9) +**Confidence:** HIGH on architecture (UnifiedBridge already shipped in lance-graph-callcenter; smb_unified_bridge is the working reference); MED on the SmbBridge promotion path (depends on `OGIT/NTO/SMB/` namespace landing upstream); HIGH on the medcare critical-path (D-UB-7 / D-UB-8 are concrete and bounded). +**Plan file:** `.claude/plans/unified-bridge-consumer-migration-v1.md` +**Predecessors:** `super-domain-rbac-tenancy-v1` (Tier A D-SDR-1..5 + §13.1 PolicyRewriter compositor) + +### Scope + +Three consumers (woa-rs, smb-office-rs, MedCare-rs) converge to one constructor pattern: `_unified_bridge(registry, actor_role, tenant) -> Result>`. CAM-codebook + schema + label + verbs from OGIT + OWL/DOLCE materialised as `OgitFamilyTable` and persisted as a LanceDB column under the `lance-cache` feature. D-UB-1..11 across three tiers; D-UB-7 (fix `ontology_dto.rs:85`) is the critical path; D-UB-8 (RLS coverage for Treatment / Visit / VitalSign) is safety-critical. + +--- + +## 2026-05-21 — lance-graph-in-woa-rs-v1 (greenfield consumer integration) + +**Status:** Active (DRAFT — six phases, additive) +**Confidence:** HIGH on architecture (every step mirrors MedCare-rs or smb-office-rs reference); MED on Phase-4 ceiling (Cypher/SPARQL surface may or may not be wanted in production). +**Plan file:** `.claude/plans/lance-graph-in-woa-rs-v1.md` +**Predecessors:** `super-domain-rbac-tenancy-v1` (substrate); `unified-bridge-consumer-migration-v1` (Tier B D-UB-4) + +### Scope + +Lift woa-rs from zero-baseline (today: OGIT TTL vendored, sea-orm + MySQL writer-parity, no lance-graph dep) to "ontology + RBAC + Lance-third-writer" via six additive phases. Phase 0 mechanical (vendor + exclude). Phase 1 lands `woa-bridge` + `woa-ontology` crates. Phases 2-3 wire route handlers + Lance projection. Phases 4-5 opt-in Cypher + CAM-PQ. Respects 2026-05-15 DualSink-Pivot (MySQL stays authoritative; Lance is a third witness). + +--- + +## 2026-05-21 — lance-graph-in-smb-office-rs-v1 (consumer integration completion) + +**Status:** Active (DRAFT — five phases, finish what unified_bridge_wiring's doc-comments already promise) +**Confidence:** HIGH (most substrate ships — smb-bridge + smb-ontology + auth + rls features wired; smb_unified_bridge is the working reference for the cross-consumer migration plan). +**Plan file:** `.claude/plans/lance-graph-in-smb-office-rs-v1.md` +**Predecessors:** `super-domain-rbac-tenancy-v1` (Tier A); `unified-bridge-consumer-migration-v1` (Tier A D-UB-2 + Tier B D-UB-5) + +### Scope + +Phase A ships dedicated `SmbBridge` upstream (~50 LOC + 2 tests). Phase B authors `OGIT/NTO/SMB/` TTL + SMB-shaped role groups (`tax_clerk` / `partner` / `client_user` / `audit_observer`) per D-SDR-2 + swaps `smb_unified_bridge` from `UnifiedBridge` to `UnifiedBridge` (15-LOC type-parameter change). Phase C consolidates rich `auth::TenantId` ↔ transparent `callcenter::TenantId`. Phases D-E opt-in Cypher / CAM-PQ. Smallest delta of the three consumer plans. + +--- + +## 2026-05-21 — lance-graph-in-medcare-rs-v1 (consumer integration in flight) + +**Status:** Active (DRAFT — seven phases, critical path on Phase 1 + 2) +**Confidence:** HIGH on architecture; CRITICAL on Phase 1 (lance-phase2 build is broken at `ontology_dto.rs:85` — blocks everything); SAFETY-CRITICAL on Phase 2 (3 newly-OGIT-surfaced Healthcare entities have no RLS policy → fail-OPEN bypass risk); HIGH on Phase 5 (LanceProbe-side endpoints D-LGMC-7..11 are concrete per super-domain plan §18.7). +**Plan file:** `.claude/plans/lance-graph-in-medcare-rs-v1.md` +**Predecessors:** `super-domain-rbac-tenancy-v1` (Tier H D-SDR-35..39); `unified-bridge-consumer-migration-v1` (Tier C D-UB-6..10); lance-graph#355 (post-PR-355 migration arc) + +### Scope + +Phase 1 fix the lance-phase2 build (`MedcareOntology::default()` calls broken no-arg form). Phase 2 close RLS fail-OPEN for Treatment / Visit / VitalSign. Phase 3 ship `medcare_unified_bridge` constructor. Phase 4 wire `MulThresholdProfile::MEDICAL` + `ontology_context_id` third axis (§73 SGB V Überweisung). Phase 5 unblock LanceProbe M2..M6 with the 5 medcare-rs endpoints (D-LGMC-7..11). Phases 6-7 opt-in Cypher / CAM-PQ. Two-branch reality: `main` (full lance-phase2) vs `claude/scaffold-medcare-rs-rZD5A` (lean fallback) — most deliverables land on `main` only. diff --git a/.claude/plans/lance-graph-in-medcare-rs-v1.md b/.claude/plans/lance-graph-in-medcare-rs-v1.md new file mode 100644 index 00000000..6bcb8879 --- /dev/null +++ b/.claude/plans/lance-graph-in-medcare-rs-v1.md @@ -0,0 +1,129 @@ +# lance-graph in MedCare-rs — v1 + +> **Author:** main thread (Opus 4.7), session 2026-05-21 (branch `claude/activate-lance-graph-att-k2pHI`) +> **Status:** Draft +> **Scope:** Close the four open items in `medcare-bridge/src/lib.rs` ("What is NOT wired yet") and complete the lance-phase2 wiring. Current state: `medcare-bridge` crate exists with `MedcareRegistry::hydrate()` + `MedcareBridge` re-exports under `--features ontology`; the canonical-branch (`main`) build is broken at `medcare-analytics::ontology_dto::MedcareOntology::default()`; the lean-fallback branch (`claude/scaffold-medcare-rs-rZD5A`) compiles without lance-graph deps. The LanceProbe C# parity tool (`MedCareV2/MedCare_2.0/LanceProbe/`) is scaffolded (M1 done, M2-M6 pending five medcare-rs endpoints). +> **Path:** `.claude/plans/lance-graph-in-medcare-rs-v1.md` +> **Confidence:** Working (architecture — most upstream substrate ships; medcare-bridge crate exists). Partial (build broken at one call site; RLS coverage incomplete for 3 newly-OGIT-surfaced entities → fail-OPEN risk). Conjecture (3DES column inventory beyond `u_pwd` — see §18.5 of super-domain plan). + +--- + +## 1 — Why this exists + +MedCare-rs is **mid-migration toward the unified-bridge end-state** — further along than woa-rs but with a known broken build blocking forward progress. Per `MedCare-rs/crates/medcare-bridge/src/lib.rs` "## What is NOT wired yet": + +1. **Build broken at `medcare-analytics::ontology_dto.rs:85`.** `MedcareOntology::default()` calls the now-broken no-arg `upstream_medcare_ontology()` form. The lance-phase2 build does not compile. Headline blocker. +2. **`ALL_SCHEMAS` ↔ `OntologyRegistry::enumerate("Healthcare")` migration incomplete.** `medcare-server::state::rls_registry` still iterates the hardcoded 4-entity list (Patient / Diagnosis / LabResult / Prescription). OGIT surfaces 3 NEW Healthcare entities (Treatment / Visit / VitalSign) which therefore have no RLS policy → **fail-OPEN bypass risk**. Safety-critical. +3. **`MulThresholdProfile::MEDICAL` not consumed at the gate site** (per D-ONTO-V5-9 in lance-graph#355). +4. **`ontology_context_id`-keyed RLS extension missing** (third axis after `(table, praxis_id)`) per §73 SGB V Überweisung shape. + +Plus the LanceProbe-side gaps from `super-domain-rbac-tenancy-v1.md` §18.7: + +5. **`POST /api/__parity/csharp` ingest endpoint absent** (blocks LanceProbe M5). +6. **`GET /api/__parity` canonical dashboard absent** (depends on #5). +7. **`_dto_contracts.md` not published** (blocks LanceProbe M2). +8. **`legacy-tripledes-fallback` feature flag DRAFT only** (blocks LanceProbe M5a). +9. **`/api/__parity/telemetry` endpoint absent** (blocks LanceProbe M6). + +This plan owns items 1–4 directly and cross-references items 5–9 to `super-domain-rbac-tenancy-v1.md` Tier H (D-SDR-35..39) where they already have spec coverage. + + +## 2 — Phasing + +### Phase 1 — Unblock the lance-phase2 build (1 day) — CRITICAL PATH + +- **D-LGMC-1** — Fix `medcare-analytics::ontology_dto.rs:85`. The broken call is `MedcareOntology::default() → upstream_medcare_ontology()` (no-arg form, removed upstream). Replace with a registry-driven constructor: `MedcareOntology::from_registry(&medcare_bridge::MedcareRegistry)`. Default impl now requires explicit registry construction — `MedcareOntology` becomes `Option` at the State level, populated only when the ontology feature is active. ~30 LOC fix in `medcare-analytics` + 2 tests + 1 propagation change in `medcare-server::state` constructor. This is `D-UB-7` from `unified-bridge-consumer-migration-v1.md`. **No other deliverable in this plan compiles until this lands.** + +### Phase 2 — Close the RLS fail-OPEN window (2 days) — SAFETY-CRITICAL + +- **D-LGMC-2** — Replace `medcare-analytics::soa_mapping::ALL_SCHEMAS` iteration in `medcare-server::state::rls_registry` with `OntologyRegistry::enumerate("Healthcare")`. The dynamic projection covers all 7 Healthcare entities (Patient, Diagnosis, LabValue, Medication, Treatment, Visit, VitalSign) — adding the 3 newly-OGIT-surfaced (Treatment / Visit / VitalSign) that today bypass RLS. ~80 LOC + 4 tests including the `rls_coverage_parity_with_all_schemas` parity test already wired in `medcare-bridge/Cargo.toml [dev-dependencies] medcare-analytics`. This is `D-UB-8`. **Safety-critical: HIPAA non-conformance until this lands.** +- **D-LGMC-3** — Boot-time enumeration assertion. Gate `medcare-server` startup on `HydrationReport.namespace_entity_count("Healthcare") >= 7` (the OGIT-canonical entity count). Mirrors the F2-A RLS-registry boot pattern already documented in `medcare-bridge::registry::MedcareRegistry::hydrate_with_report`. If the count drifts (TTL adds an entity, RLS coverage falls behind), boot fails closed. ~40 LOC + 2 tests (success path; failure path). + +### Phase 3 — Complete the unified-bridge constructor (1 day) + +- **D-LGMC-4** — Add `medcare-bridge/src/unified_bridge_wiring.rs::medcare_unified_bridge(registry, actor_role, tenant) -> Result, _>`. Mirrors `smb_unified_bridge` but parameterised over `MedcareBridge` (not `OgitBridge`). actor_role enumerates per `super-domain-rbac-tenancy-v1.md` §4.3: `physician` / `nurse` / `cashier` / `researcher` / `hipaa_audit` / `admin`. ~60 LOC + 4 integration tests covering each role's redaction-mask behaviour. This is `D-UB-6`. Depends on D-LGMC-1. + +### Phase 4 — MUL MEDICAL + ontology_context_id (3 days) + +- **D-LGMC-5** — `MulThresholdProfile::MEDICAL` consumption at the gate site. Per D-ONTO-V5-9 in lance-graph#355: the medical profile sets the Dunning-Kruger / TrustTexture / FlowState thresholds appropriate for clinical-decision contexts (more conservative than the default `MulThresholdProfile::BALANCED`). Plumb into the `UnifiedBridge::authorize` audit emission so the audit log records which profile was active. ~60 LOC + 2 tests. This is `D-UB-9`. +- **D-LGMC-6** — `ontology_context_id`-keyed RLS extension. Third axis after `(table, praxis_id)` per §73 SGB V Überweisung shape. The use case: a Patient's Diagnosis row created under Praxis A is visible to Praxis B if and only if Praxis A issued a §73 SGB V referral (the `ontology_context_id` carries the referral identity). Adds a new column to the Healthcare Lance dataset + a third clause to the row-filter predicate. ~100 LOC + 4 tests (intra-praxis; cross-praxis with referral; cross-praxis without referral; expired referral). This is `D-UB-10`. + +### Phase 5 — LanceProbe-side endpoints (4 days) — UNBLOCKS C# PARITY TOOL + +These are the medcare-rs deliverables already named in `super-domain-rbac-tenancy-v1.md` Tier H (D-SDR-35..39). Reproduced here as the smaller "what does this plan ship in MedCare-rs" view: + +- **D-LGMC-7** — `POST /api/__parity/csharp` ingest endpoint. Receives `DriftEvent` JSON from LanceProbe's `DriftSink` batch flushes; persists to a Lance table for cross-session aggregation. ~150 LOC + 4 tests. **Blocks LanceProbe M5.** Cross-ref: `D-SDR-35`. +- **D-LGMC-8** — `GET /api/__parity` canonical dashboard endpoint. Aggregates drift events across sessions (group-by route + time bucket); JSON output for the admin-only ParityPanel. ~120 LOC + 3 tests. **Blocks LanceProbe M5.** Cross-ref: `D-SDR-36`. +- **D-LGMC-9** — `_dto_contracts.md` document. Stable JSON DTO contracts for the 5 pilot endpoints (Patient / Lab / Vital / Diagnosis / Wartezimmer) + the planned 40+ additional routes per `MedCare-rs/docs/FUTURE_STACK_ADMIN.md` §4. Doc only, ~300 lines markdown. **Blocks LanceProbe M2.** Cross-ref: `D-SDR-37`. +- **D-LGMC-10** — `legacy-tripledes-fallback` feature flag (currently DRAFT in `AUTH_LEGACY_TRIPLEDES_MIGRATION.md`). When enabled, accepts both Argon2 and legacy-TripleDES password verification; backfills Argon2 on successful legacy auth. Per `super-domain-rbac-tenancy-v1.md` §18.5: the legacy "3DES" is actually broken-3DES-equivalent-to-single-DES with 56-bit effective key strength, so the migration is **upgrade-on-login** (not bulk rewrap-to-AES-GCM). ~180 LOC + 6 tests covering the upgrade-on-login flow + the legacy ciphertext carry-forward. **Blocks LanceProbe M5a.** Cross-ref: `D-SDR-38`. Needs human security review before merge. +- **D-LGMC-11** — `/api/__parity/telemetry` endpoint exposing flat TelemetrySnapshot equivalent for programmatic monitoring (LanceProbe's flat struct). ~80 LOC + 2 tests. **Blocks LanceProbe M6.** Cross-ref: `D-SDR-39`. + +### Phase 6 — Cypher / SPARQL surface (5 days, opt-in) + +- **D-LGMC-12** — Wire `lance-graph-planner` as `medcare-bridge` workspace dep behind `[features] planner`. Add `POST /api/graph/query` endpoint that accepts Cypher / SPARQL over the Healthcare Lance projection. Tenant-isolation predicate auto-injected per `UnifiedBridge::authorize` lowering to DataFusion §3.10. ~150 LOC + 4 tests. +- **D-LGMC-13** — Cohort similarity rewrites of three flagship clinical queries: + - "patients with similar comorbidity profiles" (Patient × Diagnosis joins) + - "lab-value trends across a Treatment cohort" (LabValue + Treatment × Visit joins) + - "medication-interaction warnings across a patient timeline" (Medication × Patient.history) + + Each is currently expressed as hand-rolled sea-orm / DataFusion. Re-expression as Cypher demonstrates the planner equivalence + uses the per-family codebook for O(1) entity-type lookup. ~120 LOC of new handlers + 6 parity tests against the existing reference path. + + +### Phase 7 — CAM-PQ similarity surface (3 days, opt-in) + +- **D-LGMC-14** — `EntityStore::similar_to(entity_id, limit)` over the Healthcare Lance projection. Uses CAM-PQ with the per-Patient Vsa16kF32 fingerprint summarising demographics + comorbidities + medication history. Clinical use case: "patients similar to this one for cohort suggestion." ~200 LOC + 4 tests. Feature-gated behind `medcare-bridge/cam-pq`. **Researcher role only** per `super-domain-rbac-tenancy-v1.md` §13.5 — direct identifiers always hashed, k-anonymity ≥ 10 (Healthcare default), DP noise on aggregates. + +## 3 — Branch strategy (two-target reality) + +Per `MedCare-rs/CLAUDE.md` "Branch + PR conventions": + +| Deliverable scope | Branch | Notes | +|---|---|---| +| Fix `ontology_dto.rs:85` build (D-LGMC-1) | `main` ONLY | Pulls the full lance-phase2 stack; scaffold (lean) branch cannot host this | +| RLS coverage close (D-LGMC-2, D-LGMC-3) | `main` (cherry-pick to scaffold only if scaffold ever consumes the registry) | Safety-critical; defer scaffold cherry-pick until scaffold needs it | +| `medcare_unified_bridge` constructor (D-LGMC-4) | `main` ONLY | Behind `--features ontology` | +| MUL MEDICAL + ontology_context_id (D-LGMC-5, D-LGMC-6) | `main` ONLY | Pulls upstream dependencies | +| LanceProbe-side endpoints (D-LGMC-7..11) | Start on scaffold (lean / safer) for the endpoint shells; cherry-pick to `main` for the full integration | Per CLAUDE.md "first PR to scaffold, then cherry-pick to main" | +| Cypher / SPARQL surface (D-LGMC-12, D-LGMC-13) | `main` ONLY | Planner dep | +| CAM-PQ similarity (D-LGMC-14) | `main` ONLY | CAM-PQ dep | + +Two-branch reality means every `main`-only deliverable is also a "does NOT land on scaffold" decision — the scaffold stays the production-grounded subset. D-LGMC-1's fix must explicitly preserve the scaffold's compile path (the `--no-default-features` build). + +## 4 — Cross-references + +- `super-domain-rbac-tenancy-v1.md` §3.9 (`UnifiedBridge::authorize`) — what `medcare_unified_bridge` returns +- `super-domain-rbac-tenancy-v1.md` §4.3 — Healthcare role matrix (`physician` / `nurse` / `cashier` / `researcher` / `hipaa_audit` / `admin`) +- `super-domain-rbac-tenancy-v1.md` §13.4 — `Healthcare ↔ OSINT` hard-lock partner row (3 layers of defense) +- `super-domain-rbac-tenancy-v1.md` §13.5 — Researcher role: anonymized projection + k-anonymity floor + DP noise +- `super-domain-rbac-tenancy-v1.md` §16 — Two-track migration (John Doe billing/tickets vs `u_pwd` only) +- `super-domain-rbac-tenancy-v1.md` §18 — Empirical MedCare + MedCareV2 inspection: LanceProbe shape, the 6 concrete canonicalization rules, D-SDR-35..39 medcare-rs deliverables +- `unified-bridge-consumer-migration-v1.md` D-UB-6..10 — sister deliverables +- `MedCare-rs/CLAUDE.md` §Architectural commitments — Iron Rule 4 ("Thinking lives only in lance-graph"); Iron Rule 1 ("MySQL is the permanent oracle / parity witness"); Iron Rule 5 ("Approval gate for upstream lance-graph changes") +- `MedCare-rs/docs/POST_PR355_FINDINGS.md` — full triage of the post-#355 state, including the four "not yet wired" items this plan closes +- `MedCare-rs/docs/CSHARP_HANDOFF_PROMPT.md` — the LanceProbe coordination spec (M1..M6 milestones) +- `MedCare-rs/docs/AUTH_LEGACY_TRIPLEDES_MIGRATION.md` — DRAFT plan that D-LGMC-10 promotes to Active + +## 5 — Open questions + +1. **`MedcareOntology` `Option<>` vs builder.** D-LGMC-1 makes `MedcareOntology` populated only when `--features ontology` is active. Does `medcare-server` boot need an explicit "ontology-required mode" gate that fails closed if no registry is provided, or is the silent skip acceptable for the lean fallback? +2. **`ontology_context_id` storage shape.** D-LGMC-6 adds a third axis. Is `ontology_context_id` a TenantId-style transparent u32 or a richer struct (referral_id + issued_by + valid_until + scope)? Closer to richer struct given the §73 SGB V semantics, but adds complexity. +3. **`u_pwd` is the only encrypted column.** Per §18.5 of the super-domain plan, confidence is "likely very few or none" beyond `u_pwd`. Needs a focused grep of `MySQL_Connect.cs::EncryptMessage()` call sites in MedCareV2 (~721 KB file). If other columns surface, D-LGMC-10's scope expands. +4. **LanceProbe DTO contract bus.** D-LGMC-9 publishes stable JSON DTO contracts for 5 pilot endpoints. The 40+ additional routes per `FUTURE_STACK_ADMIN.md §4` need enumeration — likely a Sonnet grindwork pass. +5. **MedCareV2 cannot be reshaped.** Per `super-domain-rbac-tenancy-v1.md` §18.1 correction: MedCareV2 is MedCare verbatim + LanceProbe overlay, with explicit "do NOT refactor" constraint. Implications for D-LGMC-7 / D-LGMC-8 / D-LGMC-11: the ingest + dashboard + telemetry endpoint shapes must match what LanceProbe already targets (HTTP+JSON over JWT — Flight SQL is Phase 5+). + +## 6 — Status + +- **Phase 1:** Not started. **Critical path; blocks everything else.** ~1 day. +- **Phase 2:** Not started. Safety-critical (HIPAA non-conformance window open until D-LGMC-2 lands). ~2 days. +- **Phase 3:** Not started. Blocked on Phase 1. ~1 day. +- **Phase 4:** Not started. Blocked on Phase 1. ~3 days. +- **Phase 5:** Some scaffolding in place (`AUTH_LEGACY_TRIPLEDES_MIGRATION.md` DRAFT). Unblocks LanceProbe M2..M6. ~4 days total. +- **Phase 6:** Phase 4-dependent. Opt-in (~5 days). +- **Phase 7:** Phase 6-dependent. Opt-in (~3 days). + +**Confidence:** Working — substrate ships upstream and the four "what is NOT wired" items in `medcare-bridge/src/lib.rs` are concrete and bounded. D-LGMC-1's fix is mechanical (replace one Default impl); D-LGMC-2's fix is the safety-critical one. Once Phase 1+2 land, the rest fans out in parallel. + +## 7 — One-line summary + +> Critical path is D-LGMC-1 (unblock the lance-phase2 build by fixing `ontology_dto.rs:85`) → D-LGMC-2 (close the RLS fail-OPEN window for Treatment / Visit / VitalSign) → D-LGMC-4 (ship the `medcare_unified_bridge` constructor); the LanceProbe-side endpoints (D-LGMC-7..11) are the second-track work that unblocks the C# parity tool's M2..M6 milestones; Cypher + CAM-PQ surfaces are opt-in after the spine is stable. + diff --git a/.claude/plans/lance-graph-in-smb-office-rs-v1.md b/.claude/plans/lance-graph-in-smb-office-rs-v1.md new file mode 100644 index 00000000..39f7779b --- /dev/null +++ b/.claude/plans/lance-graph-in-smb-office-rs-v1.md @@ -0,0 +1,91 @@ +# lance-graph in smb-office-rs — v1 + +> **Author:** main thread (Opus 4.7), session 2026-05-21 (branch `claude/activate-lance-graph-att-k2pHI`) +> **Status:** Draft +> **Scope:** Complete the lance-graph integration in smb-office-rs from the current partial state (`smb-bridge` + `smb-ontology` crates exist, `smb_unified_bridge` constructor exists parameterised over `OgitBridge`; smb-bridge implements `EntityStore` + `EntityWriter` for Mongo + Lance; auth + RLS features wired against `lance-graph-callcenter`/`lance-graph-ontology`/`lance-graph-rbac`). End-state: dedicated `SmbBridge` in `lance-graph-ontology::bridges` locked to a future `OGIT/NTO/SMB/` namespace; SMB-shaped role groups (`tax_clerk` / `partner` / `client_user` / `audit_observer`) per D-SDR-2 in the upstream plan; Cypher / SPARQL surface over the Lance projection of MongoDB collections. +> **Path:** `.claude/plans/lance-graph-in-smb-office-rs-v1.md` +> **Confidence:** Working — most substrate already in tree. Partial — the dedicated `SmbBridge` does not yet exist upstream; the rich `auth::TenantId` vs `lance_graph_callcenter::TenantId` consolidation is deferred per `smb-bridge/src/unified_bridge_wiring.rs` lines 16-25. + +--- + +## 1 — Why this exists + +smb-office-rs is the **most-progressed consumer** of the lance-graph spine outside MedCare-rs. Per `smb-office-rs/CLAUDE.md`: + +- F1–F4 phases ship smb-core → smb-bridge → smb-cache → smb-ontology against the lance-graph workspace already vendored as a sibling clone. +- `smb-bridge` implements `lance-graph-contract::repository::{EntityStore, EntityWriter}` for MongoDB (German BSON wire format with `kdnr`, `firma`, `vorname` field names) AND Lance (Foundry-shape projection). +- `smb-bridge` ships an `auth` feature wiring JWT → `ActorContext` via the `lance-graph-callcenter::auth-jwt` feature (PR #273 lance 2→4 + datafusion 51→52 + deltalake 0.30→0.31 unblocked). +- `smb-bridge` ships an `rls` feature against the `auth-rls` feature in lance-graph-callcenter. +- `smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge` is **the working reference** for the cross-consumer unified-bridge migration plan. + +What's missing: + +1. **Dedicated `SmbBridge` in `lance-graph-ontology::bridges`.** Today `smb_unified_bridge` is parameterised over `OgitBridge::for_namespace(...)` (a pass-through). The `unified_bridge_wiring.rs` doc-comment lines 9-14 names the future swap explicitly. +2. **`OGIT/NTO/SMB/` namespace TTL.** Doesn't exist yet upstream. SMB callers currently lock onto `Network` or `WorkOrder` namespaces shared with other consumers. +3. **SMB-shaped role groups.** Per `unified_bridge_wiring.rs` lines 53-57: "D-SDR-2 will extend this with the SMB-specific role groups (e.g. `tax_clerk`, `partner`, `client_user`, `audit_observer`) once the `RoleGroup` + `FieldRedactionMask` types ship." +4. **Cypher / SPARQL surface.** Today's Lance projection in `smb-bridge::lance` is reachable via DataFusion SQL through lance-graph-catalog but no first-class Cypher endpoint is wired. +5. **`auth::TenantId` rich-tenant ↔ `callcenter::TenantId(u32)` consolidation.** Per `unified_bridge_wiring.rs` lines 16-25: "Future consolidation: when the SMB ontology ships its own SmbBridge, fold the rich tenant context into a super_domain cross-walk so callers see one type." + +This plan owns those five items. + + +## 2 — Phasing + +### Phase A — Upstream `SmbBridge` skeleton (3 days, lance-graph workspace) + +- **D-LGSMB-1** — Add `lance-graph-ontology/src/bridges/smb_bridge.rs` mirroring `medcare_bridge.rs` (44 lines) verbatim with `NAMESPACE = "SMB"` and `bridge_id() = "smb"`. Add `pub mod smb_bridge;` to `bridges/mod.rs` and the `pub use smb_bridge::SmbBridge;` re-export. ~50 LOC + 2 tests (BridgeFromRegistry round-trip; cross-namespace-leak detection). This is also `D-UB-2` from `unified-bridge-consumer-migration-v1.md`. +- **D-LGSMB-2** — TTL placeholder at `AdaWorldAPI/OGIT/NTO/SMB/entities/` — at least one entity (e.g., `ogit.SMB:TaxClient` extending `ogit.Network:Person`) so the namespace registers at hydration. Real TTL authoring is Phase B. Lands as an OGIT-fork PR. + +### Phase B — `SmbBridge` parameterisation swap + role groups (4 days, smb-office-rs side) + +- **D-LGSMB-3** — Swap `smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge` from `UnifiedBridge` → `UnifiedBridge`. Per the doc-comment promise, call sites stay unchanged — only the type parameter shifts. ~15 LOC change + 1 regression test asserting the previous OgitBridge surface still resolves correctly (no behaviour change at consumers). This is `D-UB-5` from `unified-bridge-consumer-migration-v1.md`. +- **D-LGSMB-4** — Author `OGIT/NTO/SMB/entities/*.ttl` for the SMB business model: `TaxClient` (the Mandant-like concept), `BookingEntry` (FiBu Buchung), `BankStatement`, `TaxReturn`, `WorkSession`, `Document` (DMS), `Reminder` (Mahnung). Includes SemanticType + ObjectView + Marking annotations per the smb-ontology pattern. ~7 TTL files + an OGIT-fork PR + corresponding entries in `smb-office-rs/crates/smb-ontology/src/`. +- **D-LGSMB-5** — SMB-shaped role groups in `lance-graph-rbac::policy`. Promote `smb_policy()` from the starter (`accountant` / `auditor` / `admin`) to the full set per D-SDR-2: `tax_clerk` / `partner` / `client_user` / `audit_observer` plus retain the starter three. Each role gets a `FieldRedactionMask` keyed against the new SMB-namespace slots (booking-entry amounts visible to `accountant` + `partner` but redacted for `client_user`; tax-return drafts writable only by `tax_clerk` and `partner`; everything readable+audit-logged for `audit_observer`). ~120 LOC + 8 tests covering each role's read/write/redact matrix. + +### Phase C — Tenant-type consolidation (2 days) + +- **D-LGSMB-6** — Cross-walk between `smb_bridge::auth::TenantId` (rich payload: tenant + scope + per-request context) and `lance_graph_callcenter::TenantId(u32)` (transparent). Today the conversion is implicit at every `smb_unified_bridge` call site (`praxis_id` / `kdnr` numeric handles). Centralise into a `From<&auth::TenantId> for callcenter::TenantId` impl on the smb-bridge side plus a `super_domain_context` cross-walk that lets the rich-tenant context ride through `UnifiedBridge::authorize`'s audit emission. ~80 LOC + 4 tests covering the four flow shapes (auth-only / authz-only / dual / mismatch). + +### Phase D — Cypher / SPARQL surface (5 days, opt-in) + +- **D-LGSMB-7** — Wire `lance-graph-planner` as smb-bridge workspace dep behind `[features] planner`. Add `POST /v1/graph/query` endpoint that accepts Cypher / SPARQL / GQL / Gremlin (polyglot detection per planner Strategy #18) and returns Arrow batches. ~150 LOC + 4 tests against the SMB Lance projection. +- **D-LGSMB-8** — Cypher rewrites of two flagship cross-collection queries: + - "all open Mahnungen for TaxClients whose latest BankStatement has unresolved entries" — currently hand-rolled across `db_mahnung` + `db_kunde` + `db_kontoauszug` mongo reads, expressible as a single Cypher MATCH against the Lance projection. + - "BookingEntries by SKR04 account category with audit-log entries from the last quarter" — currently two-step (db_buchungen scan + db_audit join), one Cypher MATCH after the planner is wired. + ~100 LOC of new handlers + 4 parity tests against the existing MongoDB reference. + +### Phase E — CAM-PQ similarity surface (3 days, opt-in) + +- **D-LGSMB-9** — `EntityStore::similar_to(entity_id, limit)` over the Lance projection. SMB use case: "TaxClients with similar booking patterns" for sales / collections segmentation. Backed by per-entity Vsa16kF32 over `BookingEntry` history. ~200 LOC + 4 tests. Feature-gated behind `smb-bridge/cam-pq`. + +## 3 — Cross-references + +- `super-domain-rbac-tenancy-v1.md` §3.9 (`UnifiedBridge::authorize`) — what `smb_unified_bridge` returns after D-LGSMB-3 +- `super-domain-rbac-tenancy-v1.md` §3.6 (`RoleGroup` + `FieldRedactionMask`) — D-LGSMB-5 implementation surface +- `super-domain-rbac-tenancy-v1.md` §13.4 — `WorkOrderBilling ↔ OSINT` hard-lock partner row (SMB inherits this) +- `unified-bridge-consumer-migration-v1.md` D-UB-2, D-UB-5 — sister deliverables +- `lance-graph-in-medcare-rs-v1.md` — parallel migration in the Healthcare super domain +- `smb-office-rs/CLAUDE.md` Iron Rule 3 — "`lance-graph` is additive-only"; this plan only adds, never edits +- `smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs` — the working reference (lines 9-14 promise D-LGSMB-3; lines 16-25 promise D-LGSMB-6; lines 53-57 promise D-LGSMB-5) + +## 4 — Open questions + +1. **OGIT/NTO/SMB/ namespace structure.** Is `SMB` the locked name or do we prefer the German `Steuerberater` (closer to the actual business domain)? Affects D-LGSMB-1 + D-LGSMB-2. +2. **`tax_clerk` permission floor.** D-LGSMB-5 maps `tax_clerk` to `READ` + `WRITE` on TaxReturn drafts. Does the SMB practice require `EXPORT` too (e.g., for DATEV submission)? Affects the Phase B `PermissionSet` bits. +3. **`client_user` scope.** The `client_user` role represents the end-customer of an SMB tax firm (someone whose Buchhaltung the firm handles). Is `client_user` allowed to see their own BookingEntries cross-period or scoped to the current fiscal year only? Affects RLS predicate complexity. +4. **Mongo retire timeline.** Per `smb-office-rs/CLAUDE.md` stage H — "the in-tree twin at `SMB-Office/smb-core-rs` is best deleted at stage H". This plan operates pre-stage-H assuming Mongo stays the writer-oracle through Phase D; Phase E onwards may shift assumptions. + +## 5 — Status + +- **Phase A:** Not started. Mechanical (3 days). Cross-listed as D-UB-2 in the sister plan. +- **Phase B:** Not started. Blocked on Phase A. ~4 days; the TTL authoring is the labour-intensive part. +- **Phase C:** Not started. Independent of Phases A/B (~2 days). +- **Phase D:** Not started. Phase B-dependent. Opt-in (~5 days). +- **Phase E:** Phase D-dependent. Opt-in (~3 days). + +**Confidence:** Working — the smb-bridge + smb-ontology + auth + rls features are already wired. This plan is mostly finishing what `unified_bridge_wiring.rs`'s doc-comments already promise. + +## 6 — One-line summary + +> Promote `smb_unified_bridge` from `UnifiedBridge` to `UnifiedBridge` (the doc-comments promise this swap), ship SMB-shaped role groups (`tax_clerk` / `partner` / `client_user` / `audit_observer`) per D-SDR-2, author `OGIT/NTO/SMB/` TTL upstream, and optionally light up the Cypher + CAM-PQ surfaces over the existing Lance projection. Smallest delta of the three consumer plans; most of the substrate already ships. + diff --git a/.claude/plans/lance-graph-in-woa-rs-v1.md b/.claude/plans/lance-graph-in-woa-rs-v1.md new file mode 100644 index 00000000..efd36513 --- /dev/null +++ b/.claude/plans/lance-graph-in-woa-rs-v1.md @@ -0,0 +1,132 @@ +# lance-graph in woa-rs — v1 + +> **Author:** main thread (Opus 4.7), session 2026-05-21 (branch `claude/activate-lance-graph-att-k2pHI`) +> **Status:** Draft +> **Scope:** Full integration of lance-graph (ontology + planner + runtime + CAM-PQ) into `woa-rs` from the current zero-baseline (today: OGIT TTL vendored under `vendor/ogit/v02-harvest/`, sea-orm + MySQL writer-parity established per RFC v02-006, no lance-graph dep, no `woa-bridge`/`woa-ontology` crates). End-state: woa-rs route handlers consume `UnifiedBridge` for ontology resolution + RBAC + tenant isolation, with the WorkOrder namespace registry persisted via the `lance-cache` LanceDB column, and a long-arc path to Cypher/SPARQL query over the same LanceDB store (orthogonal to MySQL writer-parity per RFC v02-001 §writer-parity-pivot Goldstaub 2026-05-15). +> **Path:** `.claude/plans/lance-graph-in-woa-rs-v1.md` +> **Confidence:** Working (architecture — `lance_graph_ontology::bridges::WoaBridge` already exists in lance-graph, locked to NAMESPACE="WorkOrder"); Partial (consumer crate scaffolding absent in woa-rs; OGIT NTO/WorkOrder TTL exists per §1.6 of `super-domain-rbac-tenancy-v1.md`); Conjecture (planner integration depth — woa-rs may stop at the ontology+RBAC tier and never wire Cypher). + +--- + +## 1 — Why this exists + +woa-rs is the rust transcode of WoA (Python/Flask/SQLAlchemy). Today the workspace ships: + +- Root `woa-rs` binary + library + 6 `crates/*` members (`decimal_money`, `skr_data`, `buchungs_validator`, `woa_pdf`, `datev_encoder` planned). +- Sea-orm + MySQL writer-parity (per the 2026-05-15 DualSink-Pivot in `.claude/board/Goldstaub.md` — Python and Rust write to the SAME MySQL). +- OGIT TTL vendored at `vendor/ogit/v02-harvest/entities/*.ttl` + `vendor/ogit/v02-harvest/POLICY.md`. +- RFC v02-006 (route codegen + ontology unification) — DRAFT, classifier landed, skeleton `crates/codegen/` not yet built. +- A single `tests/ontology_cypher_round_trip.rs` integration test — the only existing lance-graph touch-point. + +What's missing: **no Cargo dep on lance-graph-contract or lance-graph-ontology**, no `crates/woa-bridge`, no `crates/woa-ontology`. Per `super-domain-rbac-tenancy-v1.md` §4.2 table row "`woa-rs` | WorkOrderBilling | WorkOrder | SOX | Bridge shipped" — that "Bridge shipped" refers to `lance_graph_ontology::bridges::WoaBridge` (the upstream side), not a consumer crate inside woa-rs. The consumer side is the work this plan owns. + +The end-state goal is **the same shape MedCare-rs is mid-migration toward** (per `MedCare-rs/CLAUDE.md` Architectural commitment #4 "Thinking lives only in lance-graph. No -thinking duplicate crate"): woa-rs supplies the route shapes + SoA columns; lance-graph supplies the matrix; sea-orm/MySQL stays the writer-parity oracle. + + +## 2 — Target workspace layout + +``` +woa-rs/ +├── Cargo.toml (workspace manifest — adds vendor/lance-graph exclude block per MedCare-rs §workspace-exclude pattern) +├── vendor/ +│ ├── ogit/v02-harvest/ (existing — TTL source of truth) +│ └── lance-graph/ (NEW — softlink to AdaWorldAPI/lance-graph fork, declares own [workspace] so excluded) +├── crates/ +│ ├── decimal_money/ (existing) +│ ├── skr_data/ (existing) +│ ├── buchungs_validator/ (existing) +│ ├── woa_pdf/ (existing) +│ ├── codegen/ (RFC v02-006 — not yet built) +│ ├── woa-ontology/ (NEW Tier B — declarations: SemanticType + ObjectView + per-property Marking per the SMB pattern at smb-office-rs/crates/smb-ontology) +│ └── woa-bridge/ (NEW Tier B — implements lance-graph-contract::{EntityStore, EntityWriter} for sea-orm/MySQL + Lance projection; ships unified_bridge_wiring per unified-bridge-consumer-migration-v1.md D-UB-4) +└── src/ (existing root binary + lib + routes/*) +``` + +The `vendor/lance-graph` softlink mirrors `MedCare-rs/vendor/lance-graph` and is the lockfile-package-collision-avoidance pattern that `MedCare-rs/Cargo.toml` `exclude` documents. Same softlink approach for `vendor/ndarray` if ndarray ever becomes a direct dep (today only transitive via lance-graph). + +## 3 — Phasing (six phases, additive) + +### Phase 0 — Workspace vendor + exclude (1 day) + +- **D-WLG-1** — Add `vendor/lance-graph` softlink (or git-submodule) to `AdaWorldAPI/lance-graph@main`. Append `exclude = ["vendor/lance-graph", "vendor/ndarray"]` to root `Cargo.toml [workspace]`. Mirror `MedCare-rs/Cargo.toml` exclude-block comment verbatim — same rationale (multiple-workspace-roots avoidance + lockfile-package-collision). ~5 LOC Cargo.toml + 0 tests (exclude-only change validated by `cargo metadata` passing). +- **D-WLG-2** — Workspace dep declarations: `lance-graph-contract = { path = "vendor/lance-graph/crates/lance-graph-contract" }`, `lance-graph-ontology = { path = "vendor/lance-graph/crates/lance-graph-ontology" }`, with `optional = true` per the medcare-bridge pattern (`MedCare-rs/crates/medcare-bridge/Cargo.toml` `[features] ontology = [...]`). Lets the lean fallback build skip the transitive lance-graph dep graph until features are flipped on. ~10 LOC Cargo.toml + 1 test (`cargo check --features=ontology` succeeds; `cargo check` without features still succeeds and the lance-graph deps are unreachable in dep tree). + +### Phase 1 — woa-bridge skeleton + ontology declarations (3 days) + +- **D-WLG-3** — `crates/woa-bridge/` new crate, mirroring `smb-office-rs/crates/smb-bridge` shape: + - `src/lib.rs` — re-exports `lance_graph_ontology::{bridges::WoaBridge, NamespaceBridge, OntologyRegistry}` per the medcare-bridge `#[cfg(feature = "ontology")] pub use ...` pattern. + - `src/registry.rs` — `WoaRegistry { registry: Arc, bridge: WoaBridge }` + `hydrate(ttl_root)` + `hydrate_with_report(ttl_root)`. Mirrors `MedCare-rs/crates/medcare-bridge/src/registry.rs::MedcareRegistry` 1:1 with `NAMESPACE = "WorkOrder"` swapped in. + - `src/unified_bridge_wiring.rs` — `woa_unified_bridge(registry, actor_role, tenant) -> Result>`. Mirrors `smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge` but parameterised over `WoaBridge` (not `OgitBridge`). + - `Cargo.toml` — gates everything behind `[features] default = []` + `ontology = ["dep:lance-graph-ontology", "dep:lance-graph-contract", "dep:lance-graph-rbac", "dep:lance-graph-callcenter"]`. + - ~200 LOC + 6 tests (hydration; bridge round-trip; unified-bridge constructor; ontology-feature-off lean fallback compiles; constructor errors on unhydrated registry; one HIPAA-equivalent SOX-redaction-mask round-trip). +- **D-WLG-4** — `crates/woa-ontology/` new crate, mirroring `smb-office-rs/crates/smb-ontology` shape. Builds a `lance-graph-contract::ontology::Ontology` for the WorkOrder namespace, attaching `SemanticType` + `ObjectView` + per-property `Marking` annotations. Concrete entities: Customer, WorkOrder, Position, HistoryEntry, Setting (Tenant), User, Document. Each entity gets a `Marking` per the GDPR / SOX shape (no PHI flags). ~250 LOC + 4 tests. + +### Phase 2 — Route-handler integration (4 days) + +- **D-WLG-5** — Wire `woa_unified_bridge` into route handlers as Tower middleware. Add `pub struct OntologyState { pub bridge: UnifiedBridge }` to `src/state.rs` (or wherever woa-rs holds Axum state). Plumb through `Extension` in handlers that need ontology resolution (today: zero handlers; the Phase-3 route-codegen output per RFC v02-006 routes.yaml will be the natural consumers). ~80 LOC + 2 tests against a fake hydrated registry. +- **D-WLG-6** — Mandant ↔ TenantId mapping. WoA's existing `Mandant.id` (numeric MySQL primary key) maps to `lance_graph_callcenter::TenantId(u32)`. Add a `From for TenantId` impl + the inverse for `Mandant::find_by_id(TenantId)`. ~40 LOC + 2 tests. Cross-ref RFC-003 (tenant type-state) — the `Customer` invariant rides on this mapping. +- **D-WLG-7** — Permission ↔ actor_role mapping. WoA's permissions (`buchhaltung`, `mandant_admin`, `kasse`, etc.) map to the `actor_role: &'static str` strings the unified bridge takes. Add `pub fn actor_role_from_user(u: &CurrentUser) -> &'static str` covering each WoA permission. ~50 LOC + 4 tests covering each role transition. + + +### Phase 3 — Lance-cache + writer-parity Lance-side projection (5 days) + +- **D-WLG-8** — Enable `lance-graph-ontology[lance-cache]` feature in `woa-bridge`. Lance dataset at `/.ontology-cache.lance/` (per `unified-bridge-consumer-migration-v1.md` §4.2 schema). Mirrors the medcare path. Boot strategy: TTL-first with Lance-as-mirror (woa-rs is greenfield so cold-start latency is acceptable; HIPAA-style audit traceability is not required but the same dataset makes a useful SOX evidence trail). ~100 LOC + 4 tests (round-trip via the dataset; idempotent re-hydration; checksum mismatch invalidation; Lance-fallback when TTL root missing). +- **D-WLG-9** — Lance-side projection of WoA's MySQL tables. Implement `lance-graph-contract::repository::EntityWriter` for the Customer, WorkOrder, Position, Tenant, User entities — projecting sea-orm `ActiveModel` writes into a parallel Lance dataset. This is the **woa-rs side of writer-parity** (per the 2026-05-15 DualSink-Pivot: Python and Rust both write MySQL; this adds Lance as a third witness without becoming authoritative). Mirrors `smb-office-rs/crates/smb-bridge/src/lance.rs` shape. ~400 LOC + 8 parity tests (per-entity insert; per-entity update; per-entity soft-delete; row-count parity against MySQL). Feature-gated behind `woa-bridge/lance` (off by default; on only in environments wanting the third witness). +- **D-WLG-10** — RLS policy build via `OntologyRegistry::enumerate("WorkOrder")`. Mirrors the medcare-rs critical gap (D-UB-8 in `unified-bridge-consumer-migration-v1.md`) but woa-rs greenfield so no fail-OPEN window — RLS lands fresh against the enumerated entity set. ~80 LOC + 4 tests. Per-entity `(table, tenant_id)` predicates plus optional `(table, tenant_id, ontology_context_id)` third axis for cross-tenant Mahnwesen / Logbook visibility. + +### Phase 4 — Cypher / SPARQL surface (10 days, opt-in) + +- **D-WLG-11** — Wire `lance-graph-planner` as workspace dep behind `[features] planner = ["dep:lance-graph-planner"]`. Planner's 16 strategies cover Cypher / GQL / Gremlin / SPARQL parsing → DataFusion. Initial surface: a single `POST /api/__graph` endpoint that accepts a Cypher string and returns Arrow batches. ~150 LOC + 4 tests (Cypher MATCH against a fake hydrated Customer set; SPARQL via the same endpoint with content-negotiation; error path for invalid syntax; tenant-isolation predicate auto-injected per the `UnifiedBridge::authorize` 4-stage flow lowering to DataFusion §3.10). +- **D-WLG-12** — Cross-table queries: WoA's "find all Mahnungen for Customers whose Vorgang.status = 'rechnung_offen' across all tenants this user has access to" — currently this is a hand-rolled sea-orm join. Re-express as Cypher; verify the planner returns identical results to the sea-orm reference. ~80 LOC test + 2 production handlers as examples (Mahnwesen listing + Stundenzettel aggregation). **This is the "lance-graph is the obligatory spine" lift** — the moment Cypher returns to a route handler, the spine is real for WoA. + +### Phase 5 — CAM-PQ similarity surface (5 days, opt-in) + +- **D-WLG-13** — `EntityStore::similar_to(entity_id, limit)` over Lance + CAM-PQ. WoA use case: "customers similar to Customer X" for sales-pipeline recommendations. Backed by per-entity Vsa16kF32 fingerprints (the Customer fingerprint summarises Customer.address + Customer.industry + recent-Vorgang-history). ~250 LOC + 4 tests. Feature-gated behind `woa-bridge/cam-pq`. +- **D-WLG-14** — Cohort-style similarity for Logbook entries: "customers with similar incident histories." Same shape as D-WLG-13 but over Logbook. ~150 LOC + 2 tests. + +## 4 — Build invariants (per super-domain-rbac-tenancy-v1.md §19) + +| Layer | Pin | +|---|---| +| `rust-version` | `1.94.1` (lance-graph workspace MSRV; portable_simd patterns) | +| `lance` | `=4.0.0` (exact pin) | +| `lancedb` | `0.27.2` (caret, PR #275) | +| `ndarray` | path = `vendor/ndarray` (softlink, if used; otherwise transitive via lance-graph) | +| SIMD layer | `ndarray::simd::*` (canonical; never raw `std::simd` or hand-rolled intrinsics) | + +The woa-rs `Cargo.toml` currently sets `rust-version = "1.95"`. Phase 0 either bumps the lance-graph workspace to 1.95 (preferred — woa-rs's pin is the tighter constraint) or backs woa-rs to 1.94.1 (acceptable as a transient until both align). + +## 5 — Cross-references + +- `super-domain-rbac-tenancy-v1.md` §3.9 (`UnifiedBridge::authorize`) — what `woa_unified_bridge` returns +- `super-domain-rbac-tenancy-v1.md` §13.1 (`PolicyRewriter` composition) — what the authorize chain composes onto +- `super-domain-rbac-tenancy-v1.md` §19 (pinned versions + ndarray::simd) — Phase 0 build constraints +- `unified-bridge-consumer-migration-v1.md` D-UB-4 — `woa_unified_bridge` constructor (this plan's Phase 1) +- `lance-graph/crates/lance-graph-ontology/src/bridges/woa_bridge.rs` — the `WoaBridge` type this plan's bridge wraps +- `woa-rs/rfcs/v02-006-route-codegen-and-ontology-unification.md` — orthogonal RFC; route codegen consumes the same OGIT TTL surface but doesn't itself require lance-graph runtime. Phase 2 of this plan is the natural counterpart to Phase 1 of v02-006. +- `woa-rs/.claude/board/Goldstaub.md` 2026-05-15 ("DualSink-Pivot") — writer-parity invariant this plan respects (MySQL stays authoritative; Lance is a third witness, not a replacement) +- `MedCare-rs/Cargo.toml` workspace-exclude block — verbatim pattern this plan's Phase 0 mirrors +- `smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs` — the working reference for the Phase-1 constructor + +## 6 — Open questions + +1. **Phase 4 ceiling.** Is Cypher/SPARQL via `lance-graph-planner` actually wanted in woa-rs production, or is the goal stopping at Phase 3 (ontology + RBAC + Lance-as-third-writer) for the foreseeable future? Affects ~10 days of Phase 4 effort. +2. **Mandant ↔ TenantId surjection.** WoA's `Mandant.id` is `i32` not `u32`. Negative values are absent in production data but Rust's type system can't see that. D-WLG-6 needs either a `TryFrom` (with `MandantIdNegative` error variant) or a workspace invariant that asserts non-negative at the sea-orm boundary. +3. **Permission table evolution.** WoA's permissions are stored in `User.permissions: JSON` per the Python source. The `actor_role_from_user` mapping (D-WLG-7) assumes a fixed enumeration; if WoA adds a new permission tomorrow, where does the new `actor_role` slot live? Likely in a `RoleGroup` registry that hydrates from a small TTL/YAML at boot, mirroring how MedCare-rs handles `physician` / `nurse` / etc. +4. **rust-version alignment.** woa-rs is on 1.95; lance-graph workspace on 1.94.1. Bumping lance-graph to 1.95 is the cleanest path but needs a workspace-wide PR. Pinning woa-rs to 1.94.1 is the temporary alternative. + +## 7 — Status + +- **Phase 0:** Not started. Mechanical (~1 day). +- **Phase 1:** Not started. Blocked on Phase 0. Mostly mirroring known patterns from MedCare-rs and smb-office-rs (~3 days, low risk). +- **Phase 2:** Not started. Blocked on Phase 1. Per-handler integration is the labour-intensive part (~4 days). +- **Phase 3:** Not started. Blocked on Phase 1. Lance-side projection benefits significantly from the smb-office-rs reference (~5 days). +- **Phase 4:** Open question whether to ship at all (~10 days if yes). +- **Phase 5:** Phase 4-dependent (~5 days if Phase 4 ships). + +**Confidence:** Working — every step has a 1:1 mirror in MedCare-rs or smb-office-rs. The architecture is locked upstream; woa-rs is the consumer. + +## 8 — One-line summary + +> Five additive phases lift woa-rs from "OGIT TTL vendored, sea-orm + MySQL writer-parity" to "ontology + RBAC + Lance-third-writer with an optional Cypher/CAM-PQ surface", each step mirroring an existing pattern from smb-office-rs or MedCare-rs. Phase 0 is mechanical; Phase 1 lands the `woa-bridge` + `woa-ontology` crates; Phase 4+ is opt-in. + diff --git a/.claude/plans/unified-bridge-consumer-migration-v1.md b/.claude/plans/unified-bridge-consumer-migration-v1.md new file mode 100644 index 00000000..af8cc6d6 --- /dev/null +++ b/.claude/plans/unified-bridge-consumer-migration-v1.md @@ -0,0 +1,257 @@ +# Unified-Bridge Consumer Migration — v1 + +> **Author:** main thread (Opus 4.7), session 2026-05-21 (branch `claude/activate-lance-graph-att-k2pHI`) +> **Status:** Draft +> **Scope:** Migrate the per-consumer bridges (`woa-bridge` — not yet started; `smb-bridge` — partial; `medcare-bridge` — in flight) onto a single shared constructor pattern that yields `lance_graph_callcenter::UnifiedBridge` (already shipped). The unified bridge is the DTO mapper that pulls CAM-codebook + schema + label + verbs from OGIT (Native) and OWL/DOLCE (cross-walks) and presents them as O(1) per-`OwlIdentity` lookups, with the dictionary materialised once and persisted append-only in a LanceDB column under `lance-graph-ontology`'s `lance-cache` feature. +> **Path:** `.claude/plans/unified-bridge-consumer-migration-v1.md` +> **Confidence:** Working (architecture: super-domain plan §3.9 locked the shape and the smb-bridge wiring file is the working reference); Partial (per-consumer `_unified_bridge()` constructors are absent in woa-rs + medcare-rs, smb-bridge ships its constructor against `OgitBridge` not the future `SmbBridge`). + +--- + +## 1 — Why this exists + +`lance_graph_callcenter::UnifiedBridge` is the canonical 4-stage authorize surface (chinese-wall → super-domain → role group → slot redaction) sitting on top of `lance_graph_callcenter::policy::PolicyRewriter` (`RowFilter` / `ColumnMask` / `RowEncryption` / `DifferentialPrivacy` / `Audit`). The shape is fixed; the deliverable is **per-consumer wiring** that says "given a hydrated registry + an actor role + a tenant id, hand back a `UnifiedBridge` locked to my OGIT namespace." Today this lives only in `smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge`. The plan extends the same shape to `woa-rs` (greenfield) and `MedCare-rs` (mid-migration), and promotes each consumer from the placeholder `OgitBridge::for_namespace(...)` parameterisation to its own typed namespace bridge once the bridge ships in `lance-graph-ontology::bridges`. + +The "DTO mapper" framing in the user-facing prompt maps to two existing artefacts: + +1. **Inline per-family codebook** (`super-domain-rbac-tenancy-v1.md` §3.3 `OgitFamilyTable` + `FamilyEntry`) — one 256-slot dense array per OGIT family, slot index IS `OwlIdentity.slot()`. Each slot carries `label_uri` + `kind` + `owl_characteristics` + `dolce_marker` + `axiom_blob` + `provenance` + `verbs` INLINE. ~50–200 KB per family, ~75 families ≈ 5–15 MB resident. +2. **Lance-cache persistence** (`lance-graph-ontology::lance_cache`, `lance-cache` feature) — append-only `MappingRow` log on a Lance dataset that survives process restarts. The in-memory dictionary is rebuilt from the Lance scan in `O(rows)` once at boot; the steady state is O(1) array index. + +The migration plan unifies the three consumer wirings around this substrate without introducing a parallel enforcement path. `UnifiedBridge::authorize()` composes onto `PolicyRewriter` per `super-domain-rbac-tenancy-v1.md` §13.1; this plan only ships the **constructor** layer that each consumer's HTTP / FFI / route handler imports. + +## 2 — Current state per consumer (2026-05-21) + +| Consumer | `-bridge` crate | `-ontology` crate | `_unified_bridge()` constructor | Namespace lock | Lance-cache wired | +|---|---|---|---|---|---| +| `woa-rs` | — (greenfield) | — (greenfield) | absent | `WorkOrder` (`lance_graph_ontology::bridges::WoaBridge::NAMESPACE`) | no | +| `smb-office-rs` | `crates/smb-bridge/` (Mongo + Lance EntityStore impls; `unified_bridge_wiring.rs`) | `crates/smb-ontology/` (`customer.rs`, `mahnung.rs`, `markings.rs`) | **present** (`smb_unified_bridge`, parameterised over `OgitBridge`) | `Network` / `WorkOrder` / future `SMB` | yes (Lance feature on smb-bridge) | +| `MedCare-rs` | `crates/medcare-bridge/` (`registry.rs::MedcareRegistry`) | (none — schema declarations live in `medcare-analytics::soa_mapping`) | absent | `Healthcare` (`lance_graph_ontology::bridges::MedcareBridge::NAMESPACE`) | yes (gated `--features ontology`) | + + +### 2.1 What "in flight" means for medcare-bridge + +Per `MedCare-rs/crates/medcare-bridge/src/lib.rs` "## What is NOT wired yet" block, the medcare-side gaps are: + +1. `medcare-analytics::ontology_dto::MedcareOntology::default()` calls a now-broken `upstream_medcare_ontology()` no-arg form → **lance-phase2 build is currently broken at `ontology_dto.rs:85`**. This is the headline blocker; nothing else in the medcare plan moves until it's fixed. +2. `medcare-analytics::soa_mapping::ALL_SCHEMAS` iteration in `medcare-server::state::rls_registry` is NOT yet replaced by `OntologyRegistry::enumerate("Healthcare")`. Without the replacement, the 3 NEW entities surfaced by OGIT (Treatment, Visit, VitalSign — beyond the legacy 4 Patient/Diagnosis/LabResult/Prescription) have no RLS policy → fail-OPEN bypass risk. Safety-critical. +3. `MulThresholdProfile::MEDICAL` consumption at the gate site (per `D-ONTO-V5-9` in lance-graph#355). +4. `ontology_context_id`-keyed RLS extension (third axis after `(table, praxis_id)`) per §73 SGB V Überweisung shape. + +These four items are the medcare-rs side of this plan's Tier C. + +## 3 — Target shape: one constructor per consumer + +Every consumer ends up exposing **one** public function in its `-bridge` crate: + +```rust +// woa-rs/crates/woa-bridge/src/unified_bridge_wiring.rs (NEW) +pub fn woa_unified_bridge( + registry: Arc, + actor_role: &'static str, // mandant / sachbearbeiter / kasse / buchhaltung / admin + tenant: TenantId, // mapped from Mandant.id +) -> Result, lance_graph_ontology::Error>; + +// smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs (EXISTS) +pub fn smb_unified_bridge( + registry: Arc, + namespace: &str, // "Network" | "WorkOrder" | future "SMB" + actor_role: &'static str, // accountant / auditor / admin (D-SDR-2: + tax_clerk / partner / client_user / audit_observer) + tenant: TenantId, // mapped from auth::TenantId.praxis_id or kdnr +) -> Result, lance_graph_ontology::Error>; + +// MedCare-rs/crates/medcare-bridge/src/unified_bridge_wiring.rs (NEW) +pub fn medcare_unified_bridge( + registry: Arc, + actor_role: &'static str, // physician / nurse / cashier / researcher / hipaa_audit / admin + tenant: TenantId, // mapped from Praxis.id +) -> Result, lance_graph_ontology::Error>; +``` + +The signature shape is uniform; the parameterisation differs by which concrete `NamespaceBridge` impl each consumer uses. smb-office-rs is the **outlier**: it ships against `OgitBridge::for_namespace(...)` because `SmbBridge` does not yet exist in `lance-graph-ontology::bridges` (see `unified_bridge_wiring.rs` lines 9-14). woa-rs + MedCare-rs ship against their dedicated `WoaBridge` / `MedcareBridge` directly. + +### 3.1 Why uniform signature matters + +Each consumer's HTTP / FFI / route-handler layer imports the constructor and stops there. Call sites stay unchanged when the bridge type parameter switches (e.g., when smb-office-rs promotes to a dedicated `SmbBridge` locked to `OGIT/NTO/SMB/`). The 4-stage authorize flow (`authorize(owl, row_tenant, op) → RowAccess`) lives on the `UnifiedBridge` regardless of which `B` is plugged in. The DataFusion predicate lowering (`super-domain-rbac-tenancy-v1.md` §3.10) is independent of `B`. + + +## 4 — DTO mapper layer (CAM codebook + schema + label, O(1) per OwlIdentity) + +The "unified as DTO mapper" framing in the user-facing prompt materialises as the **per-family codebook tables** described in `super-domain-rbac-tenancy-v1.md` §3.3. Concretely: + +``` +Hydration phase (boot or first registry.resolve()): + AdaWorldAPI/OGIT/NTO//entities/*.ttl + + AdaWorldAPI/OGIT/NTO//verbs/*.ttl + + AdaWorldAPI/OGIT/NTO//attributes/*.ttl + + OWL/DOLCE cross-walks from MetaAnchors + │ + ▼ + parse_ttl_directory_with_provenance ← in lance-graph-ontology::ttl_parse + │ + ▼ + MappingProposal stream ← in lance-graph-ontology::proposal + │ + ▼ + OntologyRegistry.append_proposals ← in lance-graph-ontology::registry + ├─ in-memory dict keyed by (bridge_id, public_name) + └─ Lance dataset (under `lance-cache` feature) — append-only + │ + ▼ + Per-family bake: OgitFamilyTable[OgitFamily] { entries: [Option; 256], codebook: PerFamilyCodebook } + ↑ ↑ + slot index IS OwlIdentity.slot() 5-8 bit centroids + (per-family local) + +Hot path (every Cypher/SPARQL/Gremlin query that touches a row): + LanceDB row → OwlIdentity (u16) → owl.family() → OgitFamilyTable lookup → FamilyEntry { label_uri, kind, axiom_blob, verbs, codebook entry } + ↑ ↑ + high-byte index into O(1) array index into the 256-slot + static [OgitFamilyTable; 256] dense array (no map lookup) +``` + +**Per-row LanceDB overhead is 6 bytes** (tenant_id u32 + owl_id u16). The codebook + label + schema + verbs do NOT live on each row — they live ONCE in the static OgitFamilyTable, addressed by the 2-byte owl_id. This is the "O(1) lookup cached in lancedb column" property: the table is materialised at boot, persisted by the `lance-cache` feature so re-hydration is O(rows) once not O(rows × ttl-parse-cost), and consulted by the same masked u16 compare DataFusion lowers Cypher MATCH to (§3.10). + +### 4.1 OWL/DOLCE cross-walk surface + +The `MetaAnchors` field (super-domain-rbac §3.5) on each `SuperDomainEntry` carries pointers to upper ontologies: + +| Cross-walk | Field | Consulted when | +|---|---|---| +| Foundry ObjectType | `foundry_object_type: Option<&'static str>` | Customer requests Foundry-shape export | +| OWL upper class | `owl_upper_class: Option<&'static str>` | OWL reasoner runs over the same registry | +| DOLCE marker | `dolce_marker: DolceMarker` (Endurant / Perdurant / Quality / Abstract) | Per-row philosophical-category tagging | +| Wikidata QID | `wikidata_qid: Option` | Wikidata sync / sameAs link generation | + +These are **interop crutches**, not the hot path. The 4-stage `authorize()` never consults them; the per-family codebook is the runtime fast lane. OWL/DOLCE values ride in `FamilyEntry.axiom_blob` for slots that carry semantic obligations (functional / transitive / inverseFunctional / etc., per `GLUE_LAYER_OGIT_TO_OWL_SPEC.md` 1-byte bitfield). + +### 4.2 Lance-cache persistence (the "cached in lancedb column" property) + +`lance-graph-ontology::lance_cache` (gated on `lance-cache` feature) writes a Lance dataset under `/.ontology-cache.lance/` with the schema: + +| Column | Type | Purpose | +|---|---|---| +| `bridge_id` | utf8 | Which tenant bridge (`woa` / `medcare` / `smb` / `ogit` / `spear` / `sharepoint`) | +| `namespace` | utf8 | OGIT namespace (`WorkOrder` / `Healthcare` / `Network` / ...) | +| `public_name` | utf8 | Public-facing name (`Customer`, `WorkOrder`, `Position`, ...) | +| `ogit_uri` | utf8 | Canonical OGIT URI (`ogit.WorkOrder:Customer`) | +| `kind` | u8 | `SchemaKind` (Entity / Edge / Attribute) | +| `dolce_marker` | u8 | `DolceMarker` | +| `owl_characteristics` | u8 | bitfield from `GLUE_LAYER_OGIT_TO_OWL_SPEC` | +| `provenance` | utf8 | `dcterms:source` lineage (carries off-label OSLC fit etc.) | +| `axiom_blob` | binary | OWL subClassOf / equivalentClass axioms | +| `verb_slots` | binary | outgoing verb slots within this family | +| `centroid_blob` | binary | 5-8 bit per-family codebook centroid (CAM-PQ shape) | +| `proposal_sha256` | binary[32] | idempotent dedup key — same proposal landed twice yields one row not two | +| `appended_at` | timestamp[ms] | append timestamp for audit chain | + +Reads are append-only; writes go through `MappingProposal::sha256()`-keyed dedup. Boot path: `OntologyRegistry::hydrate_once_sync(ttl_root, &[namespace])` walks TTL once and builds the in-memory registry; `lance_cache::append_proposals(...)` mirrors it to the Lance dataset; subsequent boots can skip the TTL walk by reading the Lance scan first and only re-TTL-walking if the on-disk root checksum mismatches. + +**This is what makes the codebook "O(1) cached in lancedb column":** the column store IS the cache; reads are scan-then-build-in-memory once at boot; lookups are array-index on `OwlIdentity.slot()` after. + + +## 5 — Deliverables + +### Tier A — Shared substrate (lance-graph workspace) + +Most of Tier A is already shipped or scoped under `super-domain-rbac-tenancy-v1.md` Tier A (D-SDR-1..5). This plan adds three follow-ons: + +- **D-UB-1** — Stable public constructor pattern doc at `lance-graph/.claude/knowledge/unified-bridge-consumer-pattern.md`. Specifies the signature shape (§3 above), the migration path from `OgitBridge`-parameterised → dedicated bridge, the deprecation strategy when a consumer switches its type parameter. ~250 lines markdown. READ BY: consumer-crate authors before adding a new bridge consumer. +- **D-UB-2** — `lance-graph-ontology::bridges::SmbBridge` skeleton locked to `Network` (placeholder until `OGIT/NTO/SMB/` namespace exists). Mirrors `WoaBridge` (`bridges/woa_bridge.rs:1-50`) — `bridge_id="smb"`, `g_lock = Network` for now, `BridgeFromRegistry` impl. ~50 LOC + 2 tests. Unblocks smb-bridge's promotion from `OgitBridge` parameterisation to `SmbBridge` per `unified_bridge_wiring.rs` lines 9-14. +- **D-UB-3** — `lance-graph-ontology::lance_cache::ontology_cache_schema()` returning a stable Arrow schema (the §4.2 table) + a `LanceCacheBootStrategy` (TTL-first / Lance-first / TTL-with-Lance-mirror) selector. ~150 LOC + 6 tests. Needed because each consumer makes a different choice (woa-rs Lance-first for cold-start latency, MedCare-rs TTL-first for HIPAA audit traceability). + +### Tier B — Per-consumer constructors + +- **D-UB-4** — `woa-rs/crates/woa-bridge/src/unified_bridge_wiring.rs::woa_unified_bridge`. ~50 LOC mirroring `smb_unified_bridge` but parameterised over `WoaBridge` (not `OgitBridge`). Depends on Tier A of `lance-graph-in-woa-rs-v1.md` shipping the `woa-bridge` + `woa-ontology` crate scaffolding first. + 2 integration tests (constructor errors on unhydrated registry; round-trip resolution of `Customer` → `ogit.WorkOrder:Customer`). +- **D-UB-5** — `smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge` already exists; this deliverable is the **type-parameter swap** from `UnifiedBridge` → `UnifiedBridge` after D-UB-2 lands. ~15 LOC change + 1 test asserting the previous OgitBridge surface still resolves correctly through the new SmbBridge type parameter (no regression at call sites). +- **D-UB-6** — `MedCare-rs/crates/medcare-bridge/src/unified_bridge_wiring.rs::medcare_unified_bridge`. NEW file. ~50 LOC parameterised over `MedcareBridge`. Depends on D-UB-7 (the `ontology_dto.rs:85` build-fix) landing first. + 2 integration tests. +- **D-UB-7** — Fix `medcare-analytics::ontology_dto::MedcareOntology::default()` to call a registry-driven constructor instead of the broken `upstream_medcare_ontology()` no-arg form. This is the lance-phase2 build-fix that unblocks all subsequent medcare work. ~30 LOC fix + 2 tests covering the constructor's failure mode (unhydrated registry) and the success path. Highest priority across this entire plan; everything medcare-side blocks on it. + +### Tier C — RLS coverage closure (medcare-rs only — safety-critical) + +- **D-UB-8** — Replace `medcare-analytics::soa_mapping::ALL_SCHEMAS` iteration in `medcare-server::state::rls_registry` with `OntologyRegistry::enumerate("Healthcare")`. Adds RLS coverage for the 3 NEW Healthcare entities (Treatment, Visit, VitalSign) that OGIT surfaces beyond the legacy 4 (Patient, Diagnosis, LabResult, Prescription). ~80 LOC + 4 tests covering full coverage (`Test C — rls_coverage_parity_with_all_schemas` in `medcare-bridge/Cargo.toml [dev-dependencies]` already references this contract). Blocking on D-UB-7. **Fail-OPEN bypass risk if shipped without this** — the unmapped entities have no row-level policy. +- **D-UB-9** — `MulThresholdProfile::MEDICAL` consumption at the gate site (per `D-ONTO-V5-9` in lance-graph#355). ~60 LOC + 2 tests. Cross-references `super-domain-rbac-tenancy-v1.md` §13.5 (researcher role: anonymized projection only). +- **D-UB-10** — `ontology_context_id`-keyed RLS extension. Adds a third axis to the `(table, praxis_id)` tuple per §73 SGB V Überweisung shape. ~100 LOC + 4 tests covering the cross-tenant referral case where a Patient row's `ontology_context_id` differs from the requesting tenant's id. + +### Tier D — Cross-consumer parity tests + +- **D-UB-11** — `lance-graph-ontology/tests/integration_unified_bridge_parity.rs`. Spawns three consumers in-process (in-memory `OntologyRegistry` hydrated from the same TTL root) and asserts that `_unified_bridge(...)` each return a `UnifiedBridge` that resolves the same shared concepts (e.g., `ogit.Auth:User`) to the same `OwlIdentity` across all three. ~120 LOC + 4 tests. Prevents per-consumer drift from sneaking into the dictionary layer. + + +## 6 — Sequencing + +``` + ┌─────────────────────────────────────────────────┐ + │ D-UB-7 fix ontology_dto.rs:85 (lance-phase2) │ ← unblocks medcare-side + │ 30 LOC + 2 tests · MedCare-rs │ + └────────────────────────┬────────────────────────┘ + │ + ┌────────────────────────┬───-┴────────────────────────┐ + │ │ │ +┌──▼───────────────┐ ┌──────▼──────────┐ ┌─────────────▼──────────────────┐ +│ D-UB-1 doc │ │ D-UB-2 SmbBridge│ │ D-UB-3 lance_cache schema/strat│ +│ ~250 lines │ │ skeleton ~50 LOC │ │ ~150 LOC + 6 tests │ +│ lance-graph │ │ lance-graph │ │ lance-graph │ +└──────────────────┘ └───────┬──────────┘ └────────────────────────────────┘ + │ + ┌──────────────┼──────────────┐ + │ │ │ + ┌──────▼──────┐ ┌─────▼────────┐ ┌──▼────────────────────────┐ + │ D-UB-4 woa │ │ D-UB-5 smb │ │ D-UB-6 medcare │ + │ unified- │ │ swap to │ │ unified_bridge_wiring │ + │ bridge │ │ SmbBridge │ │ NEW file (~50 LOC + 2 t) │ + │ NEW (~50 +2)│ │ (~15 LOC +1) │ │ MedCare-rs │ + └─────────────┘ └──────────────┘ └────────────┬──────────────┘ + │ + ┌──────────────────┼──────────────────┐ + │ │ │ + ┌───▼──────┐ ┌─────────▼─────┐ ┌──────────▼──────┐ + │ D-UB-8 │ │ D-UB-9 │ │ D-UB-10 │ + │ RLS │ │ MUL MEDICAL │ │ ontology_context│ + │ coverage │ │ ~60 LOC +2 t │ │ _id RLS axis │ + │ 80 LOC+4 │ │ MedCare-rs │ │ 100 LOC+4 tests │ + └──────────┘ └───────────────┘ └─────────────────┘ + + └────────────────┬───────────────┘ + │ + ┌───────────▼────────────┐ + │ D-UB-11 cross-consumer │ + │ parity test 120 LOC+4 │ + │ lance-graph │ + └────────────────────────┘ +``` + +The critical path is D-UB-7 → D-UB-6 → D-UB-8. Everything else can fan out in parallel once Tier A lands. + +## 7 — Cross-references + +- `super-domain-rbac-tenancy-v1.md` §3.9 — `UnifiedBridge::authorize` (the trait this plan wires consumers onto) +- `super-domain-rbac-tenancy-v1.md` §13.1 — `lance_graph_callcenter::policy::PolicyRewriter` (the enforcement chain UnifiedBridge composes onto, not parallels) +- `super-domain-rbac-tenancy-v1.md` §3.3 — `OgitFamilyTable` + `FamilyEntry` (the DTO mapper's storage shape) +- `super-domain-rbac-tenancy-v1.md` §3.5 — `MetaAnchors` (OWL/DOLCE/Foundry/Wikidata cross-walks) +- `super-domain-rbac-tenancy-v1.md` §14 — Bridge harvest from medcare/sharepoint as canonical pattern source +- `super-domain-rbac-tenancy-v1.md` §18.7 — `D-SDR-35..39` (medcare-rs parity ingest endpoints; orthogonal to this plan but adjacent) +- `lance-graph-in-woa-rs-v1.md` — depends on Tier A of that plan for crate scaffolding +- `lance-graph-in-smb-office-rs-v1.md` — D-UB-5 (type-parameter swap) is the smb-office-rs sequel to that plan's Tier B +- `lance-graph-in-medcare-rs-v1.md` — D-UB-7..10 are the medcare-rs deliverables for unblocking lance-phase2 and closing the RLS coverage gap + +## 8 — Open questions + +1. **SmbBridge namespace name** — The unified_bridge_wiring comment names a future `OGIT/NTO/SMB/`. Is `SMB` the locked name or do we want `Steuerberater` / `Buchhaltung` / something more specific? Affects D-UB-2. +2. **Lance-cache vs TTL-first per consumer** — woa-rs is greenfield so Lance-first is cheap; MedCare-rs needs HIPAA audit traceability so TTL-first with explicit dataset-as-mirror is safer. D-UB-3 names the selector; the per-consumer plans confirm which strategy each ships with. +3. **D-UB-8 fail-OPEN window** — Is there a way to gate `medcare-server` boot on a "registry enumeration matches expected entity count" check so the 3 NEW Healthcare entities (Treatment / Visit / VitalSign) cannot land without RLS policy? Probably yes via `HydrationReport` already returned by `medcare_bridge::registry::MedcareRegistry::hydrate_with_report`. +4. **Cross-consumer parity test scope** — D-UB-11 asserts `ogit.Auth:User` resolves identically across three consumers, but what about cross-namespace concepts (a Customer in WorkOrder vs a Patient in Healthcare — both have addresses)? Out of scope for v1; the same parity test would catch it later if needed. + +## 9 — Status + +- **Architecture:** Working — UnifiedBridge ships, smb_unified_bridge is the reference wiring, the three target signatures are uniform. +- **Tier A:** Not started. D-UB-1..3 author this session or next. +- **Tier B:** D-UB-4 + D-UB-6 not started. D-UB-5 is a 15-LOC swap once D-UB-2 lands. +- **Tier C:** D-UB-7 is the blocker; D-UB-8 is the safety-critical follow-on. D-UB-9..10 land after. +- **Tier D:** D-UB-11 ships last as the regression gate. + +**Confidence:** Working. The substrate is locked in `super-domain-rbac-tenancy-v1.md`; this plan only formalises the per-consumer wiring + closes the 4 medcare-rs gaps named in `medcare-bridge/src/lib.rs`. + +## 10 — One-line summary + +> Three consumers, three `_unified_bridge()` constructors, one `lance_graph_callcenter::UnifiedBridge` shape, one per-family codebook materialised once at boot and cached as a LanceDB append-only column. The fail-OPEN risk on the 3 newly-OGIT-surfaced Healthcare entities is the safety-critical headline; D-UB-7 → D-UB-6 → D-UB-8 is the critical path. + From c54944af6ee39f614ed3765fef18acf122b17b5d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 16:40:06 +0000 Subject: [PATCH 03/22] =?UTF-8?q?docs(plans/woa-rs):=20refine=20hot/cold?= =?UTF-8?q?=20split=20=E2=80=94=20lance-graph-ontology=20hot,=20sea-orm+My?= =?UTF-8?q?SQL=20cold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User clarification 2026-05-21: woa-rs already has sea-orm as the DTO layer + the MySQL bridge. lance-graph-ontology is the read hot path (O(1) ontology resolution via OgitFamilyTable; UnifiedBridge::authorize masked-predicate combine; CAM-PQ similarity over the Lance projection; Cypher / SPARQL via the planner). sea-orm + MySQL is the cold path (writer-parity authoritative per the 2026-05-15 DualSink-Pivot; byte-exact row values; system of record for parity tests + DATEV / X-Rechnung / PDF generation). §9 adds: the hot-path operation inventory (kind / label / authorize / similarity / referral / role redaction / Cypher); the cold-path operation inventory (insert/update/delete / read for byte-exact parity / DATEV export / migrations / bulk audit); the three concrete bridges between them (write-through projection, boot-time projection, drift reconciler); phase remapping; explicit clarification that the Lance projection is a READ replica, NOT a third writer (the DualSink-Pivot writer-parity contract stays exactly two writers: Python + Rust, both to MySQL). §9 also tightens D-WLG-9 scope (the Lance projection is replicate, not source — MySQL wins on drift) and sharpens the Phase 4 ceiling ("rewrite the 6-8 cross-entity queries that produce the most join code, leave the rest"). The existing §1-§8 framing remains valid; §9 is additive in the same shape as super-domain-rbac-tenancy-v1.md §13-§19 session appendices. --- .claude/plans/lance-graph-in-woa-rs-v1.md | 94 +++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/.claude/plans/lance-graph-in-woa-rs-v1.md b/.claude/plans/lance-graph-in-woa-rs-v1.md index efd36513..592f6ca0 100644 --- a/.claude/plans/lance-graph-in-woa-rs-v1.md +++ b/.claude/plans/lance-graph-in-woa-rs-v1.md @@ -130,3 +130,97 @@ The woa-rs `Cargo.toml` currently sets `rust-version = "1.95"`. Phase 0 either b > Five additive phases lift woa-rs from "OGIT TTL vendored, sea-orm + MySQL writer-parity" to "ontology + RBAC + Lance-third-writer with an optional Cypher/CAM-PQ surface", each step mirroring an existing pattern from smb-office-rs or MedCare-rs. Phase 0 is mechanical; Phase 1 lands the `woa-bridge` + `woa-ontology` crates; Phase 4+ is opt-in. + +--- + +## 9 — Hot path / cold path refinement (2026-05-21, same session) + +User clarification: **woa-rs already has sea-orm as the DTO layer + the MySQL bridge.** The plan is to use `lance-graph-ontology` as the **hot path** and MySQL / sea-orm as the **cold path**. This refines (does not replace) §1–§8. + +### 9.1 What "hot path" means here + +The hot path is **every per-request operation that needs to identify, authorize, label, or compare an entity at O(1) or O(log n)**. Specifically: + +| Hot-path operation | Carrier | Why hot | +|---|---|---| +| "What kind of entity is this row?" — Customer vs WorkOrder vs Position vs Tenant vs User | `OgitFamilyTable.lookup(owl_id) → FamilyEntry.kind` | one masked u16 compare → one array index. Sub-microsecond. | +| "What's the canonical OGIT URI for this row?" | `FamilyEntry.label_uri` | inline in the same `FamilyEntry`; no second fetch. | +| "What's the rdfs:label / German UI string for this row?" | `FamilyEntry.label_uri` resolved via the OWL/DOLCE cross-walk in `MetaAnchors` | static lookup, cached at boot. | +| "Can this actor read this row?" | `UnifiedBridge::authorize(owl, row_tenant, op)` — 4-stage masked-predicate combine | one DataFusion predicate vector. Sub-microsecond per row. | +| "Which rows are similar to this one?" | per-entity Vsa16kF32 + CAM-PQ scan over the Lance projection | O(log n) via codebook compression; no MySQL touch. | +| "Cross-tenant referral visibility under §73 SGB V" | `ontology_context_id` predicate (third RLS axis) | one extra masked compare in the same predicate vector. | +| "Which permissions does this actor have on this slot?" | `RoleGroup.redaction_mask.{readable,writable,redacted}_slots[slot]` | one bit-test in a 256-bit set. | +| "Cypher / SPARQL / Gremlin query" | `lance-graph-planner` → DataFusion plan over the Lance projection | planner's 16 strategies; CAM-PQ-aware. | + +**None of these hit MySQL.** They all resolve in-process from the registry (`OntologyRegistry` for the ontology surface; `Policy` for RBAC; Lance datasets under the `lance-cache` feature for persistence of the registry itself plus the per-entity projection). + +### 9.2 What "cold path" means here + +The cold path is **every operation that mutates business state OR reads a row's authoritative byte-exact value**. Specifically: + +| Cold-path operation | Carrier | Why cold | +|---|---|---| +| Insert / update / delete a Customer / WorkOrder / Position / Mahnung row | `sea-orm Entity::insert(.).exec(&db)` → MySQL | DualSink-Pivot 2026-05-15: writer-parity with the Python source is the spec. MySQL is the system of record. | +| Read a row's exact field values (`Customer.firma`, `WorkOrder.betreff`, `Position.netto_summe`) | sea-orm `find_by_id` → MySQL | the byte-exact value is what the Python source produces; parity tests in `tests/parity/` compare row-by-row. | +| DATEV export / X-Rechnung generation / PDF rendering | sea-orm reads → woa_pdf / datev_encoder | output must be byte-identical to Python; MySQL is the only source where this is true. | +| Schema migration | sea-orm migrations | the migrations are the contract between MySQL versions. | +| Bulk historical scans (audit / GoBD retention / financial year close) | sea-orm or raw SQL | reading the audit-trail authoritative state, not the hot-path projection. | + +**The cold path is authoritative.** When the hot path and the cold path disagree, the cold path wins. Drift detection rides on this asymmetry. + +### 9.3 The pipe between hot and cold + +Three concrete bridges: + +1. **Write-through projection** (D-WLG-9). Every sea-orm `Entity::insert/update/delete` on a hot-path-projected entity (Customer, WorkOrder, Position, Tenant, User) also dual-writes a Lance row via `lance-graph-contract::repository::EntityWriter`. The write is **synchronous within the same transaction boundary** for the Customer/WorkOrder/Position/Tenant/User set (the entities whose row identity needs to be visible to the next read on the same request); other entities can dual-write async. +2. **Boot-time projection** for entities not yet write-through-projected: a one-shot scan at startup reads every row from MySQL and lands it in the Lance projection. After startup, the write-through path keeps the two in sync. +3. **Drift reconciler** (cron job, opt-in): periodically scans both stores, compares MerkleRoots per-row (computed over the canonical fingerprint of each row's authoritative field set), emits a drift event for any mismatch. Mirrors the `ParityWitness` shape from `MedCareV2/MedCare_2.0/LanceProbe/` per `super-domain-rbac-tenancy-v1.md` §18.2. **Cold path wins on reconciliation:** the Lance projection is rebuilt from MySQL on conflict, never the other way around. + +### 9.4 Phase remapping (replaces §3 phase-level framing) + +The six phases stay; their internal framing tightens: + +| Phase | Hot-path delivery | Cold-path delivery | +|---|---|---| +| 0 (vendor + exclude) | none (mechanical) | none | +| 1 (woa-bridge + woa-ontology) | the `OntologyRegistry` + `WoaBridge` + `UnifiedBridge` constructor (every subsequent hot-path operation rides on this) | none | +| 2 (route-handler integration) | wire `UnifiedBridge` into route handlers as Tower middleware (`OntologyState` extension) | unchanged — sea-orm reads stay the cold-path read for byte-exact values | +| 3 (lance-cache + Lance projection) | `lance-cache` feature persists the registry as a LanceDB column (cold-start latency drops); D-WLG-9 stands up the Lance projection of MySQL tables | sea-orm + MySQL stays authoritative; **the projection is a hot-path read replica, NOT a write replacement** | +| 4 (Cypher / SPARQL) | `POST /api/__graph` endpoint over the planner → DataFusion → Lance projection | cold path unchanged | +| 5 (CAM-PQ similarity) | `EntityStore::similar_to` over Lance + CAM-PQ | cold path unchanged | + +The **write-through synchronisation** for the Customer/WorkOrder/Position/Tenant/User set lands in Phase 3 (D-WLG-9) as part of the Lance projection. The drift reconciler is a Phase 3 follow-on or Phase 4 opt-in. + +### 9.5 What this means for the existing sea-orm code + +**Nothing changes about the existing sea-orm code path.** Every route handler that today does `Customer::find_by_id(db, id).await?` keeps doing that. The hot-path integration is **additive**: a route handler that needs ontology resolution or RBAC or similarity adds an `OntologyState` extension parameter and calls `state.bridge.authorize(...)` / `state.registry.resolve(...)`; a route handler that just reads a row's fields stays on sea-orm. + +The win is at **routes that today hand-roll cross-entity joins, similarity heuristics, or permission matrices**. Those routes — Mahnwesen aggregation, Stundenzettel cross-customer rollup, "find similar customers" pipelines — become Cypher queries (Phase 4) or CAM-PQ calls (Phase 5) without the hand-rolled join code. The cold-path sea-orm code stays for the byte-exact-row reads those hot-path queries reduce TO. + +### 9.6 Consequence for D-WLG-9 scope + +D-WLG-9 in §3 was framed as "Lance-side projection of WoA's MySQL tables ... woa-rs side of writer-parity." The hot/cold split tightens this: + +- **Lance projection is the hot-path READ replica**, not a writer-parity peer. +- The 2026-05-15 DualSink-Pivot's "Python + Rust both write MySQL" stays the writer-parity contract; **Lance is NOT a third writer-parity peer**. +- D-WLG-9's parity tests compare sea-orm read → MySQL → row vs. EntityStore read → Lance → row for the Customer/WorkOrder/Position/Tenant/User entity set, asserting they agree under the write-through invariant. +- If Lance and MySQL disagree, **MySQL wins** and Lance is rebuilt from MySQL. The Lance projection is replicate, not source. + +### 9.7 Open questions (refines §6) + +1. **Sync vs async dual-write boundary.** Phase 3 needs to commit which entities sync-dual-write (request-scope-visible) vs async-dual-write (eventually-consistent). My initial pick: Customer / WorkOrder / Position / Tenant / User sync; Mahnung / Stundenzettel-Eintrag / Logbook-Eintrag / Dokument / Setting async. Needs validation against actual route-handler read-after-write patterns. +2. **Hot-path-only entities** (entities that live only in Lance, no MySQL row). E.g., per-entity Vsa16kF32 fingerprints, CAM-PQ codebooks, drift-event log. Phase 5 onwards. These do NOT have a cold-path MySQL counterpart by design. +3. **Cypher rewrite of mid-complexity routes.** The §6 question "is Cypher actually wanted in production" sharpens: yes for cross-entity queries that today hand-roll sea-orm joins; not yet for trivial single-entity find-by-id (those stay on sea-orm). The Phase 4 ceiling becomes "rewrite the 6-8 cross-entity queries that produce the most join code, leave the rest." That's ~1 week not ~2. +4. **Drift reconciler cadence.** Hourly vs nightly vs continuous. Probably nightly for v1 — Phase 4 / 5 features that depend on the projection being recent (similarity search) tolerate one-day-lagged drift if the underlying business velocity is sub-daily. + +### 9.8 Cross-references (additive) + +- `woa-rs/.claude/board/Goldstaub.md` 2026-05-15 ("DualSink-Pivot") — explicitly preserved; this section refines its read-side framing, not its writer-parity contract. +- `MedCare-rs/CLAUDE.md` § Architectural commitments #1 ("MySQL is the permanent oracle / parity witness") — the same role MySQL plays in MedCare-rs is what it plays here on the woa-rs cold path. +- `super-domain-rbac-tenancy-v1.md` §17.7 ("Net architecture summary") — the per-row LanceDB layout (tenant_id u32 + owl_id u16 + ciphertext + merkle_root cleartext) is the hot-path projection shape this plan inherits. +- `super-domain-rbac-tenancy-v1.md` §16.5 ("MerkleRoot-cleartext-beside-ciphertext") — the drift reconciler in §9.3 of this plan compares MerkleRoots between MySQL and Lance per-row. + +### 9.9 One-line summary update + +> lance-graph-ontology is the hot path (resolution + RBAC + label + similarity + Cypher in O(1) or O(log n) over the codebook + Lance projection); sea-orm + MySQL is the cold path (writer-parity authoritative; byte-exact row values; the projection rebuilds from MySQL on drift). The 2026-05-15 DualSink-Pivot writer-parity contract is preserved; Lance is a READ replica, not a third writer. + From 74a3e1a53dd67a50d30319dadc123ed35050a609 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 16:47:20 +0000 Subject: [PATCH 04/22] =?UTF-8?q?docs(plans/medcare-rs):=20refine=20=C2=A7?= =?UTF-8?q?8=20=E2=80=94=20parallelbetrieb=20already=20shipped=20+=20Mongo?= =?UTF-8?q?DB=20alt=20cold=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two user clarifications, both incorporated: 1. The sink-with-diff-monitoring infrastructure is already shipped (the original Phase 5 framing overstated the gap): - lance_graph_callcenter::transcode::parallelbetrieb (376 LOC) defines DriftKind / DriftField / DriftEvent / Reconciler. - medcare-analytics::mysql_reconciler::MedcareMysqlReconciler (461 LOC) ships Round-1 Patient on /api/patient/{id} with 11 unit tests + pluggable PatientFetcher for testability. - smb_bridge::mongo_reconciler::SmbMongoReconciler (395 LOC) is the sister C2 reconciler over MongoDB. - MedCareV2 LanceProbe (M1 complete) speaks the same DriftEvent JSON shape. - medcare-server::routes::parity ships POST /api/__parity/csharp ingest + GET /api/__parity dashboard (admin-only, 1024-event ring buffer in OnceLock>). - 5-phase F1-F5 narrative in docs/medcare-umstellung.md. Status update on the original D-LGMC-7..11: - D-LGMC-7 (ingest endpoint): SHIPPED — drop. - D-LGMC-8 (dashboard): SHIPPED — drop. - D-LGMC-9 (DTO contracts doc): still open. - D-LGMC-10 (TripleDES fallback flag): still DRAFT. - D-LGMC-11 (telemetry endpoint): still open. New Phase 5b deliverables: D-LGMC-15..18 grow the existing reconciler shell from Round-1 (Patient) to Round-2 (Lab/Vital/Diagnosis/Prescription) — only the per-route dispatch table grows. D-LGMC-19 wires production query handles. D-LGMC-20 centralises the canonicalizer table upstream. D-LGMC-21 replaces the in-process ring buffer with LanceAuditSink. 2. MongoDB is added as an ALTERNATIVE cold path (alongside MySQL, not replacing it — Iron Rule 1 preserved). Propagated from smb-office-rs's three-layer shape: - D-LGMC-22 (NEW crates/medcare-mongo/): mirrors smb-mongo (MongoImporter + MigrationStats), ~200 LOC + 4 tests. - D-LGMC-23 (NEW crates/medcare-bridge/src/mongo.rs): mirrors smb-bridge::mongo (MongoConnector impl of EntityStore + EntityWriter), ~250 LOC + 6 tests, gated [features] mongo. - D-LGMC-24 (NEW crates/medcare-analytics/src/mongo_reconciler.rs): mirrors smb-bridge::mongo_reconciler 1:1 with healthcare entities substituted, ~400 LOC + 11 tests. - D-LGMC-25 (cold-path selection config): deployment picks MySQL, MongoDB, or both, ~50 LOC + 2 tests. - D-LGMC-26 (dual-reconciler mode): when both cold paths active, triple-redundancy with 3 pairwise comparisons (MySQL↔Lance, MongoDB↔Lance, MySQL↔MongoDB), ~100 LOC + 4 tests. Hot/cold framing updated: hot = lance-graph-ontology + Lance projection; cold (A) = sea-orm + MySQL (legacy oracle, Iron Rule 1 permanent); cold (B) = MongoDB (alternative, NEW). --- .claude/plans/lance-graph-in-medcare-rs-v1.md | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/.claude/plans/lance-graph-in-medcare-rs-v1.md b/.claude/plans/lance-graph-in-medcare-rs-v1.md index 6bcb8879..daa10d7d 100644 --- a/.claude/plans/lance-graph-in-medcare-rs-v1.md +++ b/.claude/plans/lance-graph-in-medcare-rs-v1.md @@ -127,3 +127,191 @@ Two-branch reality means every `main`-only deliverable is also a "does NOT land > Critical path is D-LGMC-1 (unblock the lance-phase2 build by fixing `ontology_dto.rs:85`) → D-LGMC-2 (close the RLS fail-OPEN window for Treatment / Visit / VitalSign) → D-LGMC-4 (ship the `medcare_unified_bridge` constructor); the LanceProbe-side endpoints (D-LGMC-7..11) are the second-track work that unblocks the C# parity tool's M2..M6 milestones; Cypher + CAM-PQ surfaces are opt-in after the spine is stable. + +--- + +## 8 — Parallelbetrieb already shipped + MongoDB as alternative cold path (2026-05-21, same session) + +User clarifications, two parts: + +1. **The sink-with-diff-monitoring is already shipped.** What `lance-graph-in-medcare-rs-v1.md` Phase 5 framed as "D-LGMC-7..11 to design from scratch" is mostly already in the tree. The refinement below documents the existing state and tightens the deliverable list. +2. **MongoDB has been added as an alternative cold path** (parallel to MySQL, not replacing it). MySQL stays the permanent parity oracle per `MedCare-rs/CLAUDE.md` Iron Rule 1; MongoDB is the second cold-path option, propagated from smb-office-rs's three-layer mongo shape. The hot/cold framing for medcare-rs becomes: **hot = lance-graph-ontology + Lance projection; cold (A) = sea-orm + MySQL (legacy parity oracle); cold (B) = MongoDB (alternative).** + +### 8.1 What's already in the tree (parallelbetrieb infrastructure) + +| Layer | File | LOC | Status | +|---|---|---:|---| +| Upstream trait + DTOs | `lance_graph_callcenter::transcode::parallelbetrieb` | 376 | **Shipped.** `DriftKind` (Match / ValueDrift / ShapeDrift / MissingMysql / MissingLance), `DriftField`, `DriftEvent`, `Reconciler` trait, `validate_route()`. Self-framed as "the one deliberate transition bandaid." | +| Rust-side MedCare reconciler (C1) | `medcare-analytics::mysql_reconciler::MedcareMysqlReconciler` | 461 | **Shipped, Round-1 = Patient only.** Route `/api/patient/{id}`. 11 unit tests cover Match / ValueDrift (single & multi-field) / MissingMysql / MissingLance / ShapeDrift / unsupported / malformed / extra-segments / C#-schema-parity / parser-tests. Pluggable `PatientFetcher` for testability. | +| Sister SMB reconciler (C2) | `smb_bridge::mongo_reconciler::SmbMongoReconciler` | 395 | **Shipped, Round-1 = Customer only.** Route `/api/customer/{kdnr}`. Same `Reconciler` trait, MongoDB instead of MySQL — the cross-source pattern this plan inherits. | +| C# parity probe | `MedCareV2/MedCare_2.0/LanceProbe/` (ParityClient + ParityWitness + DriftSink) | — | **Shipped (M1).** Same `DriftEvent.ToJson()` schema across both languages. | +| medcare-rs ingest + dashboard | `medcare-server::routes::parity` — `POST /api/__parity/csharp` + `GET /api/__parity` | 94 | **Shipped.** 1024-event ring buffer in `OnceLock>`. Auth: any authenticated user POSTs; `GET` is admin-only. | +| 5-phase migration narrative | `docs/medcare-umstellung.md` | 102 | **Shipped.** F1 dual-write → F2 reconciler-live (consumers still MySQL) → F3 consumers-switch → F4 features-live → F5 RBAC. MySQL is permanent witness per Iron Rule 1. | +| CLAUDE.md Iron Rule 1 | `MedCare-rs/CLAUDE.md` § Architectural commitments | — | **Locked.** "MySQL is the permanent oracle / parity witness. It is never retired, even after F4. The reconciler witnesses every promotion gate forever." | + +### 8.2 What's still deferred (per source comments) + +- **Round-2 routes** on the existing reconciler shell: Lab / Vital / Diagnosis / Prescription. Per `mysql_reconciler.rs:7-11`: "Other routes return a `ShapeDrift` event with reason 'route not in Round-1 scope'. Lab / Vital / Diagnosis / Prescription land in follow-up PRs that all share the same `MedcareMysqlReconciler` shell — only the per-route dispatch table grows." +- **Production query handles**: Closures inside `MedcareMysqlReconciler` are unit-tested with canned rows; production needs them to wrap `medcare_db::queries::patient::get_patient(...)` for the MySQL side + the corresponding Lance read for the SPO side. +- **Canonicalizer table centralization**: 6 field rules (date-only, "F4" doubles, soft-delete coercion, second-truncated timestamps, German locale handling, ciphertext byte-compare for `u_pwd`) need to land in **one place** that both the Rust reconciler and the C# ParityWitness reference. Per `parallelbetrieb.rs:51`: "Land both rules in one place when the Rust query path is wired (Phase 3 of `.claude/plans/sql-spo-ontology-bridge-v1.md`)." +- **Persistent sink**: Today's ring buffer is in-process only (`OnceLock>`, capped 1024). Per parallelbetrieb.rs doc: "The Rust side will publish to the same persistent ring buffer (`crate::audit::LanceAuditSink`) once the wiring lands." +- **Rust-side reconciler runner**: The trait + impl exist; the **loop that periodically issues queries against both sides and feeds `Reconciler::reconcile()`** is not yet wired (deferred per `parallelbetrieb.rs:44-54`). + + +### 8.3 Phase 5 deliverable corrections (D-LGMC-7..11 status update) + +| D-id | §5 original | §8 corrected | +|---|---|---| +| D-LGMC-7 | `POST /api/__parity/csharp` ingest absent — 150 LOC + 4 tests | **SHIPPED** in `medcare-server::routes::parity::ingest_csharp`. Drop from open deliverable list. Optional follow-up: verify 1024-event ring cap is right for production load + add a `purge_after: Duration` config. | +| D-LGMC-8 | `GET /api/__parity` dashboard absent — 120 LOC + 3 tests | **SHIPPED** (same module, admin-only read of the ring buffer). Drop from open deliverable list. Optional follow-up: per-route + per-time-bucket aggregation per the original spec; today's endpoint returns the raw newest-first event stream. | +| D-LGMC-9 | `_dto_contracts.md` for 5 pilot endpoints | **STILL OPEN.** `docs/CSHARP_HANDOFF_PROMPT.md` references the contracts but doesn't enumerate the 5 pilot endpoints' JSON shapes verbatim. Per the C# handoff this blocks LanceProbe M2. | +| D-LGMC-10 | `legacy-tripledes-fallback` feature flag — DRAFT | **STILL DRAFT** per `docs/AUTH_LEGACY_TRIPLEDES_MIGRATION.md`. Blocks LanceProbe M5a. | +| D-LGMC-11 | `/api/__parity/telemetry` endpoint absent | **STILL OPEN.** No telemetry endpoint today. Blocks LanceProbe M6. | + +### 8.4 New Phase 5b deliverables (Round-2 routes on the existing reconciler shell) + +Extend `MedcareMysqlReconciler` from Round-1 (Patient) to Round-2 (Lab / Vital / Diagnosis / Prescription) by growing the per-route dispatch table. Each adds ~80 LOC of canonical row type + diff impl + tests; the reconciler shell stays untouched. + +- **D-LGMC-15** — `CanonicalLabRow` + `LabFetcher` + `parse_lab_route("/api/lab/{id}")` + diff impl. ~80 LOC + 5 tests (match / value-drift / missing-mysql / missing-lance / shape-drift). +- **D-LGMC-16** — `CanonicalVitalRow` + `VitalFetcher` + `parse_vital_route("/api/vital/{id}")` + diff impl. ~80 LOC + 5 tests. +- **D-LGMC-17** — `CanonicalDiagnosisRow` + `DiagnosisFetcher` + `parse_diagnosis_route("/api/diagnosis/{id}")` + diff impl. ~80 LOC + 5 tests. +- **D-LGMC-18** — `CanonicalPrescriptionRow` + `PrescriptionFetcher` + `parse_prescription_route("/api/prescription/{id}")` + diff impl. ~80 LOC + 5 tests. +- **D-LGMC-19** — Production query-handle wiring: replace the closure fetchers with `medcare_db::queries::{patient,lab,vital,diagnosis,prescription}::get_*(...)` for the MySQL side + the corresponding Lance reads for the SPO side. ~150 LOC + 5 integration tests against a real MySQL fixture + Lance dataset (gated behind `--features mysql-integration lance-phase2`). +- **D-LGMC-20** — Canonicalizer-table centralization: hoist the 6 field rules (date-only, "F4" doubles, soft-delete coercion, second-truncated timestamps, German locale, `u_pwd` ciphertext byte-compare) into a shared `lance_graph_callcenter::transcode::canonicalize` module so both the Rust reconciler and the C# ParityWitness reference the same source. ~120 LOC + 6 tests. **Upstream PR required** (Iron Rule 5). +- **D-LGMC-21** — Persistent sink: replace the in-process ring buffer with `lance_graph_callcenter::audit::LanceAuditSink` so drift events survive restart and feed the cross-session dashboard. ~80 LOC + 3 tests covering buffer-overflow eviction + restart-recovery + concurrent-writer ordering. + +### 8.5 New Phase 9 — MongoDB cold path (propagated from smb-office-rs) + +Add MongoDB as an **alternative** cold path. MySQL stays the permanent oracle per Iron Rule 1; MongoDB is a second cold-path option for tenants / deployments where MongoDB is the system of record (or for new clinical entities authored cleanly against the OGIT TTL shape without a MySQL ancestor). Both feed the same hot path (lance-graph-ontology + Lance projection) and both are witnessed by the same parallelbetrieb reconciler. + +Mirror the smb-office-rs three-layer shape: + +| smb-office-rs (template) | medcare-rs (mirror, NEW) | Notes | +|---|---|---| +| `crates/smb-mongo/` (connector + `MongoImporter` + `MigrationStats`, 205 LOC, depends on workspace `mongodb` + `bson`) | `crates/medcare-mongo/` (NEW) | Same workspace deps. Importer reads clinical collections (per OGIT/NTO/Healthcare entities) into in-process cache keyed by `:`. | +| `crates/smb-bridge/src/mongo.rs` (`MongoConnector` impl of `EntityStore + EntityWriter`, 313 LOC, gated `[features] mongo`) | `crates/medcare-bridge/src/mongo.rs` (NEW) | Same trait surface (`lance-graph-contract::repository::{EntityStore, EntityWriter}`); BSON wire shape carries the Healthcare-namespace entity properties from `medcare-ontology` (when that lands per D-WLG-4-equivalent for medcare). Gated `[features] mongo`. | +| `crates/smb-bridge/src/mongo_reconciler.rs` (`SmbMongoReconciler`, 395 LOC, `Reconciler` impl) | `crates/medcare-analytics/src/mongo_reconciler.rs` (NEW) | Mirrors `mysql_reconciler.rs` 1:1: pluggable `Fetcher` trait with `fetch_mongo(...)` + `fetch_lance(...)` methods; per-route dispatch table covering Round-1 Patient + Round-2 Lab/Vital/Diagnosis/Prescription; same `DriftEvent` JSON shape. | + +Concrete deliverables: + +- **D-LGMC-22** — `crates/medcare-mongo/` new crate. `MongoImporter::new(uri, db)` + `import_all() -> MigrationStats` covering Patient / Diagnosis / LabValue / Medication / Treatment / Visit / VitalSign collections. Mirrors `smb-mongo::MongoImporter` 1:1 with healthcare-namespace entity names. ~200 LOC + 4 tests (per-collection import sanity; full-import stats; error-bag captures per-collection failures without aborting; round-trip BSON roundtrip). +- **D-LGMC-23** — `crates/medcare-bridge/src/mongo.rs` new module. `MongoConnector` impl of `EntityStore + EntityWriter` for the same 7 Healthcare entities. Gated `[features] mongo = ["dep:medcare-mongo", "dep:mongodb", "dep:bson"]`. ~250 LOC + 6 tests (per-entity insert / update / soft-delete / fetch-by-id / list-by-tenant / cross-collection join via Lance projection). +- **D-LGMC-24** — `crates/medcare-analytics/src/mongo_reconciler.rs` new module. `MedcareMongoReconciler` (and equivalents per Round-2 entity), `Reconciler` impl over the same trait. Same JSON wire shape as `MedcareMysqlReconciler`. ~400 LOC + 11 tests (mirrors the existing `mysql_reconciler` test suite verbatim, MongoDB substituted for MySQL). The reconciler shell is shared; only `fetch_mongo()` differs from `fetch_mysql()`. +- **D-LGMC-25** — Cold-path selection: `medcare-server` config layer adds `cold_path: ColdPath::{MySql, Mongo}` so a deployment picks one (or both, with double-reconciler for diverse-redundancy). ~50 LOC + 2 tests (config parsing + boot-time selection). +- **D-LGMC-26** — Dual-reconciler mode: when both cold paths are active, the runner issues queries against MySQL **and** MongoDB **and** Lance, emitting a `DriftEvent` per pair. Triple-redundancy diverges into N(N-1)/2 = 3 pairwise comparisons per query (MySQL↔Mongo, MySQL↔Lance, Mongo↔Lance). ~100 LOC + 4 tests. + +### 8.6 Hot/cold framing update + +Reframes §2 of the woa-rs sister plan (`lance-graph-in-woa-rs-v1.md` §9) for medcare-rs: + +| Path | Carrier | Role | Authority | +|---|---|---|---| +| Hot | `OgitFamilyTable` lookup + `UnifiedBridge` 4-stage authorize + Lance projection scans | O(1) resolution / RBAC / Cypher / CAM-PQ similarity | Read-only; rebuilds from cold path on drift | +| Cold A (legacy oracle) | sea-orm + MySQL via `medcare-db` | System of record; byte-exact row reads; DATEV-equivalent regulatory exports | **Authoritative on drift.** Permanent per Iron Rule 1. | +| Cold B (alternative, NEW) | `medcare-bridge::mongo::MongoConnector` over MongoDB | Alternative system of record for deployments / entities authored against OGIT TTL without a MySQL ancestor | Authoritative for its own entities; reconciler witnesses MySQL↔Mongo agreement when both active | + +The parallelbetrieb reconciler now has three roles to triangulate: + +``` + ┌────────────────────────────┐ + │ Hot path │ + │ (Lance projection, │ + │ via lance-graph- │ + │ ontology) │ + └────────────┬───────────────┘ + │ + ┌──────────────┴──────────────┐ + │ Reconciler │ + │ emits DriftEvent │ + │ (Match/Value/Shape/ │ + │ Missing*) │ + └──┬────────────────────────┬─┘ + │ │ + ┌────────────┴────────┐ ┌──────────┴───────────┐ + │ Cold A: MySQL │ │ Cold B: MongoDB │ + │ (legacy oracle, │ │ (alternative, │ + │ sea-orm DTO) │ │ medcare-bridge:: │ + │ Iron Rule 1: │ │ mongo) │ + │ permanent witness │ │ │ + └─────────────────────┘ └───────────────────────┘ +``` + +Pairwise drift comparisons in dual-reconciler mode (D-LGMC-26): + +1. **MySQL ↔ Lance** — already shipped via `MedcareMysqlReconciler` Round-1. +2. **MongoDB ↔ Lance** — new via D-LGMC-24 (`MedcareMongoReconciler` Round-1 + Round-2). +3. **MySQL ↔ MongoDB** — new via D-LGMC-26 dual-mode (compares the two cold paths directly without going through Lance). + +### 8.7 Sequencing impact + +Phases 1-7 of §2 stay; Phase 5 is reduced (D-LGMC-7+8 are shipped, drop them); Phase 5b is added (D-LGMC-15..21 — Round-2 + production wiring + persistent sink); Phase 9 is new (D-LGMC-22..26 — MongoDB cold-path propagation from smb-office-rs). + +``` + ┌──────────────────────────────────────────────────────────┐ + │ Phase 1 (D-LGMC-1) fix ontology_dto.rs:85 build │ + │ critical path; nothing else compiles until this lands │ + └────────────────────────┬─────────────────────────────────┘ + │ + ┌───────────────────┼──────────────────────────────┐ + │ │ │ + ┌────▼──────┐ ┌────────▼────────┐ ┌───────────▼──────────┐ + │ Phase 2 │ │ Phase 3 (LGMC-4)│ │ Phase 5 (open items) │ + │ (LGMC-2,3)│ │ unified-bridge │ │ (LGMC-9,10,11) │ + │ RLS close │ │ constructor │ │ DTO docs + 3DES + tel│ + └────┬──────┘ └────────┬────────┘ └──────────────────────┘ + │ │ + │ ┌───────────┴────────────────┐ + │ │ │ + ┌────▼───────▼───┐ ┌──────────────▼──────────────┐ + │ Phase 4 │ │ Phase 5b (NEW) │ + │ (LGMC-5,6) │ │ Round-2 reconciler + prod │ + │ MUL MEDICAL │ │ wiring + persistent sink │ + │ + context_id │ │ LGMC-15..21 │ + └─────────────────┘ └──────────────┬──────────────┘ + │ + ┌─────────▼─────────────┐ + │ Phase 9 (NEW) │ + │ MongoDB cold path │ + │ propagation from SMB │ + │ LGMC-22..26 │ + └───────────────────────┘ +``` + +### 8.8 Open questions + +1. **Why MongoDB for medcare specifically?** smb-office-rs has it because the inherited C# WinForms ERP persisted to MongoDB with German BSON field names — the cold-path mongo is the legacy data. For medcare-rs the legacy is C# + MySQL + 3DES; what's the equivalent driver for adding MongoDB? Possibilities: + - **New clinical entities** authored cleanly against OGIT TTL with no MySQL ancestor (Treatment / Visit / VitalSign — the 3 NEW entities D-LGMC-2 closes RLS for) — ship them on MongoDB to avoid MySQL schema migration. + - **Tenant choice**: some deployments prefer MongoDB ops; per-tenant config. + - **Foundry-shape ingest path**: external systems (FHIR / HL7 / OpenEHR feeds) land BSON natively; MongoDB is the staging area before promotion to Lance. + - **Other** (please clarify). +2. **Dual-write or dual-cold-path?** If both MySQL and MongoDB are active, do writes go to both (dual-write, with the reconciler witnessing agreement) or does each entity have a single declared cold path? Affects D-LGMC-25's config shape. +3. **Per-entity cold-path routing.** If different entities have different cold paths (Patient → MySQL, Treatment → MongoDB), the bridge's `EntityWriter::write_(...)` needs to dispatch on entity type. Closer to a Router pattern than a single config switch. +4. **Reconciler triple-redundancy cost.** D-LGMC-26's three pairwise comparisons triple the reconciler load per query. Acceptable for sample-gated drift detection (default 1% per `CSHARP_HANDOFF_PROMPT.md`); needs sampling-rate tuning if dual-mode is the default. + +### 8.9 Cross-references (additive) + +- `MedCare-rs/crates/medcare-analytics/src/mysql_reconciler.rs` — the working reference for D-LGMC-15..18 (Round-2 expansion) and D-LGMC-24 (mongo sister) +- `MedCare-rs/crates/medcare-server/src/routes/parity.rs` — the shipped ingest/dashboard endpoints (drops D-LGMC-7 and D-LGMC-8) +- `MedCare-rs/docs/medcare-umstellung.md` — the 5-phase F1-F5 narrative this plan extends +- `MedCare-rs/docs/foundry-roadmap-unified-smb-medcare-v1.md` §4 — the smb→medcare mirror table that maps `MongoConnector` to `MySqlConnector` (D-LGMC-22..24 expand this to MongoConnector + MySqlConnector BOTH) +- `lance-graph/crates/lance-graph-callcenter/src/transcode/parallelbetrieb.rs` — the upstream trait (D-LGMC-20 lands the canonicalize sub-module here) +- `lance-graph/crates/lance-graph-callcenter/src/audit/` (target for D-LGMC-21 `LanceAuditSink`) — needs confirmation it ships; if absent, D-LGMC-21 first adds it upstream +- `smb-office-rs/crates/smb-mongo/` — the L1 template D-LGMC-22 mirrors +- `smb-office-rs/crates/smb-bridge/src/mongo.rs` — the L2 template D-LGMC-23 mirrors +- `smb-office-rs/crates/smb-bridge/src/mongo_reconciler.rs` — the L3 template D-LGMC-24 mirrors + +### 8.10 Status + +- **Existing parallelbetrieb infrastructure:** working — Round-1 Patient reconciler + ingest/dashboard endpoints + C# probe + 5-phase narrative all shipped. +- **Round-2 routes (D-LGMC-15..18):** not started. Mechanical extension of the existing shell (~80 LOC each). +- **Production wiring (D-LGMC-19):** not started. Blocked on D-LGMC-1 (build fix). +- **Canonicalizer centralization (D-LGMC-20):** not started. Upstream PR required. +- **Persistent sink (D-LGMC-21):** not started. Depends on `LanceAuditSink` existing upstream. +- **MongoDB cold path (D-LGMC-22..26):** not started. Mirrors three-layer smb-office-rs shape; ~1000 LOC + ~25 tests total. + +**Confidence:** Working. The reconciler architecture is locked + partially shipped; the MongoDB propagation has a verbatim template in smb-office-rs; the open questions in §8.8 are about scope / configuration, not architecture. + +### 8.11 One-line summary + +> Parallelbetrieb infrastructure is already shipped Round-1 (Patient on MySQL ↔ Lance, with the C# ParityWitness, ingest/dashboard endpoints, and 5-phase F1-F5 narrative). This refinement (a) acknowledges what ships, (b) reframes Phase 5 around Round-2 expansion + production wiring + persistent sink, (c) adds Phase 9 to propagate the smb-office-rs three-layer MongoDB shape into medcare-rs as an alternative cold path (alongside, not replacing, MySQL — Iron Rule 1 preserved). + From 29f60239fdd8a2966458d4056bb9ad5b88b6fabc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 16:56:03 +0000 Subject: [PATCH 05/22] chore(vendor): add ractor vendor import (sibling-clone softlink) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User direction 2026-05-21: adaworldapi/ractor is not yet in the session's MCP github scope ("forgot to put it in scope"); add it as a vendor import "for now" until the scope is updated. Imported via anonymous public zipball: https://api.github.com/repos/AdaWorldAPI/ractor/zipball/HEAD Source SHA: a50c675 (zipball top-level dir name; matches the SHA file at /home/user/ractor/.vendor-import-sha.txt). Layout follows the existing workspace convention from MedCare-rs/vendor/ — softlinks to sibling clones at /home/user//: vendor/.gitkeep (track empty dir) vendor/README.md (explains the temporary-import status + replacement path when MCP scope is added + wiring instructions for path deps) vendor/ractor -> ../../ractor (softlink; target is /home/user/ractor) No Cargo.toml touched — this is source-only vendoring; the [workspace] exclude block + path-dep wiring lands in the consumer's Cargo.toml when a crate actually depends on ractor. ractor itself ships a workspace with 7 members (ractor, ractor_cluster, ractor_cluster_derive, ractor_cluster_integration_tests, ractor_example_entry_proc, ractor_playground, xtask). The exclude block will be required at that wiring step to avoid the "multiple workspace roots found" cargo error documented in MedCare-rs/Cargo.toml. Cross-ref: the convergence-v1 plan (.claude/plans/cognitive-substrate-convergence-v1.md + causaledge64-mailbox-rename-soa-v1.md) names ractor as the actor substrate carrying CausalEdge64 emissions; this vendor import unblocks that work without waiting for MCP scope expansion. --- vendor/.gitkeep | 0 vendor/README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ vendor/ractor | 1 + 3 files changed, 76 insertions(+) create mode 100644 vendor/.gitkeep create mode 100644 vendor/README.md create mode 120000 vendor/ractor diff --git a/vendor/.gitkeep b/vendor/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/vendor/README.md b/vendor/README.md new file mode 100644 index 00000000..244e96d8 --- /dev/null +++ b/vendor/README.md @@ -0,0 +1,75 @@ +# `vendor/` — sibling-clone softlinks + +This directory follows the workspace convention from +`MedCare-rs/vendor/` (and the pattern documented in +`MedCare-rs/Cargo.toml`'s exclude block): vendored dependencies are +softlinks to sibling clones at `/home/user//`. The link is +stable from inside the lance-graph repo; the actual source tree +lives alongside this repo on the filesystem. + +## Current contents + +| Softlink | Target | Source SHA | Imported | Reason | +|---|---|---|---|---| +| `ractor` | `../../ractor` (i.e. `/home/user/ractor`) | `a50c675` | 2026-05-21 | **Temporary vendor import** — `AdaWorldAPI/ractor` is not yet in the session's MCP github scope. Imported via anonymous public zipball from `https://api.github.com/repos/AdaWorldAPI/ractor/zipball/HEAD`. Replace with proper MCP-scoped fetch once `adaworldapi/ractor` is added to the scope. | + +The SHA + timestamp for each import is also stored at +`/home/user//.vendor-import-sha.txt` for cross-session +visibility. + +## Why softlinks (not in-tree copies) + +The workspace convention is sibling-clone softlinks for three reasons: + +1. **Avoids `multiple workspace roots found in the same workspace`** + — every vendored dep here (`ractor`, `lance-graph` itself when + vendored elsewhere, `ndarray` when vendored) declares its own + `[workspace]` block. Excluding the softlink path from this repo's + workspace via `[workspace] exclude = ["vendor/"]` (in the + consumer's `Cargo.toml`, added when a path dep is wired) keeps + cargo happy. +2. **Repo size stays small** — a 3.4 MB source tree doesn't bloat + `lance-graph`'s commit history. +3. **Sibling clone is the single source of truth** — updates land + in `/home/user//` once and every consumer's softlink picks + them up. + +## Replacing this temporary import with the canonical path + +When `adaworldapi/ractor` is added to the MCP github scope: + +1. Confirm the sibling clone at `/home/user/ractor` is current + (re-zipball if needed, or `git clone` via the local proxy once + the proxy honors the new scope). +2. Optionally `git init` + add the `adaworldapi/ractor` remote and + `git fetch` to attach commit history. +3. The softlink + path-dep wiring in any consumer's `Cargo.toml` + stays unchanged. + +## Wiring a path dep (when needed) + +This vendor import is **source-only**; no `Cargo.toml` yet declares +a path dep on `vendor/ractor`. When a consumer crate inside this +workspace needs to depend on ractor, add (in the consumer's +`Cargo.toml`): + +```toml +[dependencies] +ractor = { path = "../../vendor/ractor/ractor" } +``` + +…and (in the root `Cargo.toml`): + +```toml +[workspace] +exclude = [ + # existing entries... + "vendor/ractor", +] +``` + +The `exclude` is mandatory because `vendor/ractor` (via softlink to +`/home/user/ractor`) declares its own `[workspace]` block with +members `ractor`, `ractor_cluster`, `ractor_cluster_derive`, +`ractor_cluster_integration_tests`, `ractor_example_entry_proc`, +`ractor_playground`, `xtask`. diff --git a/vendor/ractor b/vendor/ractor new file mode 120000 index 00000000..c0f38a98 --- /dev/null +++ b/vendor/ractor @@ -0,0 +1 @@ +../../ractor \ No newline at end of file From b3548babb970e49b1d74d80ce58617eadfc82c14 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:12:54 +0000 Subject: [PATCH 06/22] =?UTF-8?q?docs(plans/unified-bridge):=20=C2=A74=20?= =?UTF-8?q?=E2=80=94=20point=20at=20concrete=20Hydrator=20types=20(post=20?= =?UTF-8?q?PR=20#407)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #407 + the ~11 preceding bO-* feature commits shipped the concrete OWL/DOLCE/OGIT cross-walk hydrators in lance_graph_ontology::hydrators::*. §4 of this plan referenced those surfaces abstractly; this commit tightens to concrete type pointers. §4.1 (OWL/DOLCE cross-walk surface) — table now names the hydrator that populates each MetaAnchors field: - owl_upper_class + dolce_marker → hydrate_dolce (OGIT::DOLCE_V1, inherits_from: None, 17-IRI edge whitelist covering rdfs:subClassOf + owl:equivalentClass + DnS classify/role-binding + dul:hasPart/isPartOf + dul:hasTimeInterval/isObservableAt; note the canonical DOLCE+DUL Endurant→Object/Perdurant→Event rename). - foundry_object_type → hydrate_schemaorg + hydrate_fibo_be for the upper-class anchors Foundry typically maps to. - wikidata_qid → not yet in the hydrator surface; deferred until a tenant requests Wikidata sync (~50 LOC glue). §4.3 (NEW — Hydrator inventory) — full surface map: - Generic substrate: OwlHydrator (the bO-* scaffold every hydrator instantiates), MetaStructureHydrator trait, ContextBundle, EntityId, OntologySlot, HydrateErr. - Layered ontologies (L1 → L4 sector): · L1: hydrate_dolce (root, inherits_from: None) · L2: hydrate_owltime / hydrate_provo / hydrate_qudt (all inherits_from: Some(OGIT::DOLCE_V1.0)) · L3: hydrate_schemaorg (commercial-web) · Sector: hydrate_skos, hydrate_fibo_fnd, hydrate_fibo_be (FIBO BE inherits FND) - Dedicated (non-OWL): SchematronHydrator, XsdHydrator + collect_xsd_files, SkrHydrator + hydrate_skr03/skr04/skr03_bau + the three IRI prefix constants. - ZUGFeRD/Factur-X: hydrate_zugferd + hydrate_zugferd_rules (XSD + Schematron over EN16931). - Full re-export surface from the crate root shown as a single `use` block for consumer ergonomics. §4.3 also maps each plan deliverable to the hydrators it uses: - D-UB-1 names the producer-side shape. - D-UB-2 (SmbBridge) declares no hydrator dep until OGIT/NTO/SMB/ ships. - D-UB-3 (lance_cache::ontology_cache_schema) persists each hydrator's ContextBundle output as the Lance column rows. - D-UB-4..6 (per-consumer constructors) take an already-hydrated Arc; deployment chooses the menu: · woa-rs: dolce + provo + qudt + schemaorg + fibo_fnd + skr03/skr04 + future OGIT/NTO/WorkOrder. · smb-office-rs: same minus WorkOrder, plus skr03_bau + zugferd + zugferd_rules. · MedCare-rs: dolce + owltime + provo + qudt + skos + future OGIT/NTO/Healthcare. No deliverable IDs renumbered; this is a clarification of §4's referenced surface against the now-shipped types. Other plan sections unchanged. --- .../unified-bridge-consumer-migration-v1.md | 88 +++++++++++++++++-- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/.claude/plans/unified-bridge-consumer-migration-v1.md b/.claude/plans/unified-bridge-consumer-migration-v1.md index af8cc6d6..a59f0917 100644 --- a/.claude/plans/unified-bridge-consumer-migration-v1.md +++ b/.claude/plans/unified-bridge-consumer-migration-v1.md @@ -115,12 +115,12 @@ Hot path (every Cypher/SPARQL/Gremlin query that touches a row): The `MetaAnchors` field (super-domain-rbac §3.5) on each `SuperDomainEntry` carries pointers to upper ontologies: -| Cross-walk | Field | Consulted when | -|---|---|---| -| Foundry ObjectType | `foundry_object_type: Option<&'static str>` | Customer requests Foundry-shape export | -| OWL upper class | `owl_upper_class: Option<&'static str>` | OWL reasoner runs over the same registry | -| DOLCE marker | `dolce_marker: DolceMarker` (Endurant / Perdurant / Quality / Abstract) | Per-row philosophical-category tagging | -| Wikidata QID | `wikidata_qid: Option` | Wikidata sync / sameAs link generation | +| Cross-walk | Field | Consulted when | Populated by (concrete hydrator) | +|---|---|---|---| +| Foundry ObjectType | `foundry_object_type: Option<&'static str>` | Customer requests Foundry-shape export | Not auto-populated; manual cross-walk per `super-domain-rbac-tenancy-v1.md` §10 OQ-1. `lance_graph_ontology::hydrators::hydrate_schemaorg` and `hydrate_fibo_be` provide the upper-class anchors Foundry typically maps to. | +| OWL upper class | `owl_upper_class: Option<&'static str>` | OWL reasoner runs over the same registry | `lance_graph_ontology::hydrators::hydrate_dolce` (L1 root, `OGIT::DOLCE_V1`, `inherits_from: None`). Resolves `rdfs:subClassOf` / `owl:equivalentClass` chains for the upper-category subsumption (Object / Event / Quality / Abstract). | +| DOLCE marker | `dolce_marker: DolceMarker` (Endurant / Perdurant / Quality / Abstract) | Per-row philosophical-category tagging | `lance_graph_ontology::hydrators::hydrate_dolce` populates the L1 bundle; downstream hydrators (`hydrate_owltime`, `hydrate_provo`, `hydrate_qudt`, `hydrate_schemaorg`, …) `inherits_from: Some(OGIT::DOLCE_V1.0)` and align via the `dul:isClassifiedBy` / `subClassOf dul:Event|Object|Quality|Abstract` chain DOLCE preserves. **Note:** canonical DOLCE+DUL renames `Endurant → Object` and `Perdurant → Event` per the DUL header — the `DolceMarker` enum either stays on the legacy naming (with downstream consumers aware of the renaming) or migrates at the cost of one source-tree-wide find/replace. Decide before D-UB-3. | +| Wikidata QID | `wikidata_qid: Option` | Wikidata sync / sameAs link generation | Not in the current hydrator surface — would need a `hydrate_wikidata` glue (~50 LOC) following the same `OwlHydrator { g, version, domain_name, inherits_from, starting_entity_id }` pattern. Probably deferred until a tenant explicitly requests Wikidata sync. | These are **interop crutches**, not the hot path. The 4-stage `authorize()` never consults them; the per-family codebook is the runtime fast lane. OWL/DOLCE values ride in `FamilyEntry.axiom_blob` for slots that carry semantic obligations (functional / transitive / inverseFunctional / etc., per `GLUE_LAYER_OGIT_TO_OWL_SPEC.md` 1-byte bitfield). @@ -148,6 +148,82 @@ Reads are append-only; writes go through `MappingProposal::sha256()`-keyed dedup **This is what makes the codebook "O(1) cached in lancedb column":** the column store IS the cache; reads are scan-then-build-in-memory once at boot; lookups are array-index on `OwlIdentity.slot()` after. +### 4.3 Hydrator inventory (post-PR #407) + +The OWL/DOLCE/OGIT cross-walk surface this plan referenced abstractly is now concrete in `lance_graph_ontology::hydrators::*` (PR #407 + the ~11 preceding feature commits). Every hydrator follows the same shape — a free function `hydrate_(registry: &OntologyRegistry) -> Result` that constructs an `OwlHydrator { g: OGIT::.0, version: OGIT::.1, domain_name: "".to_string(), inherits_from: Option, starting_entity_id: 100 }`, calls `.hydrate(ttl_path, registry)` (or `.hydrate_many(&paths, registry)` for multi-file bundles like DOLCE+DUL+extensions), then registers an edge-IRI whitelist via `registry.register_edge_types(g, &EDGE_WHITELIST)`. The return value is the OGIT `G` slot u32 the bundle landed in. + +**Generic substrate (the bO-* scaffold every hydrator instantiates):** + +| Type | Path | Role | +|---|---|---| +| `OwlHydrator` | `lance_graph_ontology::hydrators::owl::OwlHydrator` | The generic struct with `g`, `version`, `domain_name`, `inherits_from`, `starting_entity_id`. Reads OWL/Turtle via the existing TTL parser, emits `MappingProposal`s into the registry. | +| `MetaStructureHydrator` | `lance_graph_ontology::hydrators::owl::MetaStructureHydrator` (trait) | Trait the per-ontology glue implements; `OwlHydrator` is the default impl. Pattern-D meta-structure hydration. | +| `ContextBundle` | `lance_graph_ontology::hydrators::owl::ContextBundle` | Typed bundle keyed by an OGIT `G` slot. One bundle per hydrator output. | +| `EntityId`, `OntologySlot`, `HydrateErr` | `lance_graph_ontology::hydrators::owl::*` | Per-entity slot index inside a bundle; the error enum. | + +**Layered ontologies (L1 root → L4 sector):** + +| Layer | Hydrator function | OGIT slot | `inherits_from` | TTL artifact | Edge whitelist size | +|---|---|---|---|---|---| +| **L1 upper** | `hydrate_dolce` (+ `hydrate_dolce_from`, `hydrate_dolce_from_many` for tests / multi-file) | `OGIT::DOLCE_V1.0` | `None` (root) | `data/ontologies/dul.ttl` + `dul-extensions/{conceptualization.owl, lmm-l2.owl}` | 17 IRIs (rdfs:subClassOf + owl:equivalentClass/disjointWith + DnS classify/role-binding + dul:hasPart/isPartOf + dul:hasTimeInterval/isObservableAt) | +| **L2 universal** | `hydrate_owltime` (+ `_from`) | `OGIT::OWLTIME_V1.0` | `Some(OGIT::DOLCE_V1.0)` | `data/ontologies/owltime.ttl` | — | +| **L2 universal** | `hydrate_provo` (+ `_from`) | `OGIT::PROVO_V1.0` | `Some(OGIT::DOLCE_V1.0)` | `data/ontologies/provo.ttl` | 22 IRIs (subClassOf/subPropertyOf + 8 load-bearing PROV relations: wasGeneratedBy/used/wasDerivedFrom/wasAttributedTo/wasAssociatedWith/wasInformedBy/actedOnBehalfOf/wasInvalidatedBy + qualified* + activity-lifecycle + delegation) | +| **L2 universal** | `hydrate_qudt` (+ `_from`) | `OGIT::QUDT_V1.0` | `Some(OGIT::DOLCE_V1.0)` | `data/ontologies/qudt/*` (+ quantitykinds catalogue, bO-4) | — | +| **L3 commercial-web** | `hydrate_schemaorg` (+ `_from`) | `OGIT::SCHEMAORG_V1.0` | `Some(OGIT::DOLCE_V1.0)` | `data/ontologies/schemaorg.ttl` | — | +| **Sector** | `hydrate_skos` (+ `_from`) | `OGIT::SKOS_V1.0` | `Some(OGIT::DOLCE_V1.0)` | `data/ontologies/skos.ttl` (+ DUL extension modules per bO-5) | — | +| **Sector — finance** | `hydrate_fibo_fnd` (+ `_from`) | `OGIT::FIBO_FND_V1.0` | `Some(OGIT::DOLCE_V1.0)` | FIBO Foundations | — | +| **Sector — finance** | `hydrate_fibo_be` (+ `_from`) | `OGIT::FIBO_BE_V1.0` | `Some(OGIT::FIBO_FND_V1.0)` | FIBO Business Entities | — | + +**Dedicated (non-OWL) hydrators:** + +| Type | Path | Domain | Role | +|---|---|---|---| +| `SchematronHydrator` | `lance_graph_ontology::hydrators::schematron::SchematronHydrator` | ISO Schematron rule sets | Hydrates Schematron rule patterns (asserts + reports) as ContextBundle entries; used by `hydrate_zugferd_rules` + `hydrate_zugferd_rules_from` for the EN16931 compliance rules ZUGFeRD/Factur-X invoices must satisfy. | +| `XsdHydrator` | `lance_graph_ontology::hydrators::xsd::XsdHydrator` (+ `collect_xsd_files()` helper) | XML Schema | Hydrates XSD type definitions as ContextBundle entries; used by `hydrate_zugferd` + `hydrate_zugferd_from` for the ZUGFeRD/Factur-X EN16931 invoice schema (PR-bO-16). | +| `SkrHydrator` | `lance_graph_ontology::hydrators::skr::SkrHydrator` | DATEV SKR charts of accounts | Hydrates the SKR account hierarchy from the CSV data files (`data/skr/SKR03.csv`, `SKR04.csv`, `SKR03_bau.csv`). Consumed by `hydrate_skr03` / `hydrate_skr04` / `hydrate_skr03_bau` (and their `_from` test variants). Three IRI-prefix constants exposed: `SKR03_IRI_PREFIX`, `SKR04_IRI_PREFIX`, `SKR03_BAU_IRI_PREFIX`. | + +**Re-export surface (consumers import from the crate root, not the submodule):** + +```rust +// Single import for the whole hydrator surface: +use lance_graph_ontology::{ + // generic + OwlHydrator, MetaStructureHydrator, ContextBundle, EntityId, OntologySlot, HydrateErr, + // layered + hydrate_dolce, hydrate_dolce_from, hydrate_dolce_from_many, + hydrate_owltime, hydrate_owltime_from, + hydrate_provo, hydrate_provo_from, + hydrate_qudt, hydrate_qudt_from, + hydrate_schemaorg, hydrate_schemaorg_from, + hydrate_skos, hydrate_skos_from, + hydrate_fibo_fnd, hydrate_fibo_fnd_from, + hydrate_fibo_be, hydrate_fibo_be_from, + // sector + dedicated + SchematronHydrator, + XsdHydrator, collect_xsd_files, + SkrHydrator, + hydrate_skr03, hydrate_skr03_from, + hydrate_skr04, hydrate_skr04_from, + hydrate_skr03_bau, hydrate_skr03_bau_from, + SKR03_IRI_PREFIX, SKR04_IRI_PREFIX, SKR03_BAU_IRI_PREFIX, + hydrate_zugferd, hydrate_zugferd_from, + hydrate_zugferd_rules, hydrate_zugferd_rules_from, +}; +``` + +**How this plan's deliverables use the hydrator surface:** + +- **D-UB-1** (consumer-pattern doc) names the `hydrate_(registry)` shape as the producer side of `OntologyRegistry`. Consumers do not call hydrators directly — the bridge constructor (`_unified_bridge`) takes an already-hydrated `Arc` and the deployment code chooses which hydrators to run at boot. +- **D-UB-2** (`SmbBridge` skeleton) declares no hydrator dependency; SMB locks to a future `OGIT::SMB_V1` slot. Once `OGIT/NTO/SMB/` ships, an `hydrate_smb_namespace()` follows the same `OwlHydrator { inherits_from: Some(OGIT::DOLCE_V1.0), ... }` pattern. +- **D-UB-3** (`lance_cache::ontology_cache_schema()` + `LanceCacheBootStrategy`) persists the per-hydrator `ContextBundle` output as the Lance dataset rows. Each row carries `bridge_id`, `namespace`, `public_name`, `ogit_uri`, `kind`, `dolce_marker`, `owl_characteristics`, `provenance`, `axiom_blob`, `verb_slots`, `centroid_blob` (§4.2 table) — populated by the hydrators that ran at boot. +- **D-UB-4..6** (per-consumer constructors) all take `Arc` already hydrated by `hydrate_dolce` + the appropriate L2/L3/sector hydrators for that consumer's namespace. Concretely: + - **woa-rs** boots: `hydrate_dolce` + `hydrate_provo` (audit trails, GoBD) + `hydrate_qudt` (Mengen, Stundenzahl) + `hydrate_schemaorg` (Customer / Invoice cross-walks) + `hydrate_fibo_fnd` (financial primitives) + `hydrate_skr03`/`hydrate_skr04` (chart of accounts) + future `OGIT/NTO/WorkOrder` namespace hydration. + - **smb-office-rs** boots: same as woa-rs minus the WorkOrder namespace, plus `hydrate_skr03_bau` (Baugewerbe) for construction tenants and `hydrate_zugferd` + `hydrate_zugferd_rules` for X-Rechnung output validation. + - **MedCare-rs** boots: `hydrate_dolce` + `hydrate_owltime` (Visit / Treatment chronology) + `hydrate_provo` (HIPAA audit trail) + `hydrate_qudt` (LabValue units, VitalSign measurements) + `hydrate_skos` (clinical-code thesauri — ICD-10 mappings) + future `OGIT/NTO/Healthcare` hydration. + +The bridge constructor never sees `OwlHydrator` directly — it sees a hydrated `OntologyRegistry`. The deployment / `main.rs` of each consumer chooses the hydration menu. + +--- ## 5 — Deliverables From b01a0c4900a411a8fda75820f89440d33793c6aa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:18:11 +0000 Subject: [PATCH 07/22] =?UTF-8?q?docs(plans/unified-bridge):=20=C2=A74=20r?= =?UTF-8?q?eframe=20=E2=80=94=20hydrators=20ARE=20the=20spine=20for=20O(1)?= =?UTF-8?q?=20inheritance=20from=20family=20buckets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two user clarifications, folded into §4: 1. The OWL/DOLCE cross-walk table is not "interop crutches" — it is the SOURCE MATERIAL from which lance-graph-ontology constructs the OGIT/OWL/DOLCE mapping. The hydrators are the construction tool; the cross-walk standards (DOLCE+DUL, OWL-Time, PROV-O, QUDT, schema.org, FIBO, SKOS, Schematron, XSD, SKR DATEV, ZUGFeRD) are the bricks; OGIT canonical surface (per-family codebook + inherits-from DAG + edge whitelists at OGIT::*_V1 slots) is the synthesis. 2. The hydrators + inherits_from + per-family codebook + family- bucket dense array TOGETHER form the spine that makes schema / label / codebook inheritance O(1) cheap at lookup time. §4.1 rewritten as "the source material for the OGIT mapping" — direction-of-build diagram added showing how each external standard flows through its hydrator into its OGIT::*_V1 G-slot. The MetaAnchors fields (foundry_object_type, owl_upper_class, dolce_marker, wikidata_qid) are reframed as the runtime READ SURFACE over the constructed mapping, not as the populated target. DolceMarker enum naming open question (Endurant/ Perdurant vs Object/Event per canonical DUL rename) called out explicitly as a decision needed before D-UB-3. §4.3 lead-in reframed from "the cross-walk surface is now concrete" to "the construction tool that builds the OGIT mapping is now concrete in lance_graph_ontology::hydrators::*". PR #408 reference added (NamespaceRegistry::seed_defaults wires the corresponding G-slots at boot). §4.4 NEW — locks the O(1) inheritance property: - Schema inheritance: inherits_from chain flattened into FamilyEntry.axiom_blob at hydration; query-time cost is one masked u16 + one array index = O(1). Zero chain-walks at query time. - Label inheritance: rdfs:label per-locale collapsed during the subClassOf walk at hydration; FamilyEntry.label_* reads are O(1) array index. Zero parent lookup at query time. - Codebook inheritance: per-family centroid references parent codebook by u8 offset when content distributions overlap (with Jirak-aware Berry-Esseen bound per I-NOISE-FLOOR-JIRAK). One indirection max. - Why family buckets vs flat dict: ~5ns L1-cache-resident two array indices vs ~50-100ns hash + collision + cache miss = 20× cost gap. Co-design between construction tool (hydrators) and runtime substrate (family buckets) — neither earns the property alone. Concrete consumer-side payoffs spelled out for woa-bridge, medcare-bridge, smb-bridge: pre-baked schema / label / codebook inheritance means route handlers read one FamilyEntry per row identity; no OWL reasoning, no rdfs:label walk, no Schematron re-parse at query time. --- .../unified-bridge-consumer-migration-v1.md | 81 ++++++++++++++++--- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/.claude/plans/unified-bridge-consumer-migration-v1.md b/.claude/plans/unified-bridge-consumer-migration-v1.md index a59f0917..054d1556 100644 --- a/.claude/plans/unified-bridge-consumer-migration-v1.md +++ b/.claude/plans/unified-bridge-consumer-migration-v1.md @@ -111,18 +111,59 @@ Hot path (every Cypher/SPARQL/Gremlin query that touches a row): **Per-row LanceDB overhead is 6 bytes** (tenant_id u32 + owl_id u16). The codebook + label + schema + verbs do NOT live on each row — they live ONCE in the static OgitFamilyTable, addressed by the 2-byte owl_id. This is the "O(1) lookup cached in lancedb column" property: the table is materialised at boot, persisted by the `lance-cache` feature so re-hydration is O(rows) once not O(rows × ttl-parse-cost), and consulted by the same masked u16 compare DataFusion lowers Cypher MATCH to (§3.10). -### 4.1 OWL/DOLCE cross-walk surface +### 4.1 OWL/DOLCE cross-walk — the source material for the OGIT mapping -The `MetaAnchors` field (super-domain-rbac §3.5) on each `SuperDomainEntry` carries pointers to upper ontologies: +**The cross-walk table below is not an interop crutch — it is the source material from which `lance-graph-ontology` *constructs* the OGIT/OWL/DOLCE mapping.** External standards (DOLCE+DUL, OWL-Time, PROV-O, QUDT, schema.org, FIBO, SKOS, …) ride in as TTL/OWL artifacts; the hydrators ingest them; the OGIT canonical classification (per-family codebook of §3.3, edge whitelists, inherits-from chain at `OGIT::*_V1` slots) is the *synthesis* that emerges. lance-graph-ontology is the construction site; the hydrators are the construction tool; the cross-walk standards are the bricks. -| Cross-walk | Field | Consulted when | Populated by (concrete hydrator) | +Direction of build: + +``` +External standards → Hydrator (consumes TTL/OWL/XSD/SKR) → OGIT canonical surface +───────────────────────── ────────────────────────────────── ────────────────────────────── +DOLCE+DUL.owl ──► hydrate_dolce ──► OGIT::DOLCE_V1 bundle + + DUL extensions (root, inherits_from: None) + + 17-IRI edge whitelist + +OWL-Time.ttl ──► hydrate_owltime ──► OGIT::OWLTIME_V1 bundle +PROV-O.ttl ──► hydrate_provo ──► OGIT::PROVO_V1 bundle +QUDT 2.1 ──► hydrate_qudt ──► OGIT::QUDT_V1 bundle +schema.org.ttl ──► hydrate_schemaorg ──► OGIT::SCHEMAORG_V1 bundle + (all four inherits_from: Some(OGIT::DOLCE_V1.0)) + +SKOS.ttl ──► hydrate_skos ──► OGIT::SKOS_V1 bundle +FIBO-FND ──► hydrate_fibo_fnd ──► OGIT::FIBO_FND_V1 bundle +FIBO-BE ──► hydrate_fibo_be ──► OGIT::FIBO_BE_V1 bundle + (inherits FND, not DOLCE direct) + +ISO Schematron rules ──► SchematronHydrator ──► rule-pattern bundle entries +XSD type defs ──► XsdHydrator ──► type-def bundle entries +ZUGFeRD/Factur-X (EN16931)──► hydrate_zugferd + hydrate_zugferd_rules + (composes XsdHydrator + Schematron) ──► ZUGFeRD-specific bundle +DATEV SKR 03/04/03-Bau ──► SkrHydrator + hydrate_skr* ──► account-hierarchy bundles + (SKR03_IRI_PREFIX etc.) + + The OGIT mapping IS the union of: + • OGIT::*_V1 G-slots populated by the above + • the inherits-from DAG that chains them + • the per-family codebook (§3.3) with + FamilyEntry { label_uri, kind, dolce_marker, + owl_characteristics, axiom_blob, + provenance, verbs } + at every OwlIdentity slot +``` + +The `MetaAnchors` struct on `SuperDomainEntry` (super-domain-rbac §3.5) is the **runtime read surface** over this constructed mapping — the field shapes are: + +| MetaAnchors field | What it points at | Built from which hydrator's output | Read at runtime by | |---|---|---|---| -| Foundry ObjectType | `foundry_object_type: Option<&'static str>` | Customer requests Foundry-shape export | Not auto-populated; manual cross-walk per `super-domain-rbac-tenancy-v1.md` §10 OQ-1. `lance_graph_ontology::hydrators::hydrate_schemaorg` and `hydrate_fibo_be` provide the upper-class anchors Foundry typically maps to. | -| OWL upper class | `owl_upper_class: Option<&'static str>` | OWL reasoner runs over the same registry | `lance_graph_ontology::hydrators::hydrate_dolce` (L1 root, `OGIT::DOLCE_V1`, `inherits_from: None`). Resolves `rdfs:subClassOf` / `owl:equivalentClass` chains for the upper-category subsumption (Object / Event / Quality / Abstract). | -| DOLCE marker | `dolce_marker: DolceMarker` (Endurant / Perdurant / Quality / Abstract) | Per-row philosophical-category tagging | `lance_graph_ontology::hydrators::hydrate_dolce` populates the L1 bundle; downstream hydrators (`hydrate_owltime`, `hydrate_provo`, `hydrate_qudt`, `hydrate_schemaorg`, …) `inherits_from: Some(OGIT::DOLCE_V1.0)` and align via the `dul:isClassifiedBy` / `subClassOf dul:Event|Object|Quality|Abstract` chain DOLCE preserves. **Note:** canonical DOLCE+DUL renames `Endurant → Object` and `Perdurant → Event` per the DUL header — the `DolceMarker` enum either stays on the legacy naming (with downstream consumers aware of the renaming) or migrates at the cost of one source-tree-wide find/replace. Decide before D-UB-3. | -| Wikidata QID | `wikidata_qid: Option` | Wikidata sync / sameAs link generation | Not in the current hydrator surface — would need a `hydrate_wikidata` glue (~50 LOC) following the same `OwlHydrator { g, version, domain_name, inherits_from, starting_entity_id }` pattern. Probably deferred until a tenant explicitly requests Wikidata sync. | +| `foundry_object_type: Option<&'static str>` | Foundry ObjectType string like `"PhysicalSystem"` | Cross-walk authored from `hydrate_schemaorg` + `hydrate_fibo_be` outputs (the upper-class anchors Foundry maps to). NO auto-population — per `super-domain-rbac-tenancy-v1.md` §10 OQ-1 ("Foundry ObjectType cross-walk targets … need product-side input"). | Foundry-shape export path; not by `authorize()`. | +| `owl_upper_class: Option<&'static str>` | OWL upper-class IRI like `"BiomedicalOntology"` | `hydrate_dolce`'s `OGIT::DOLCE_V1` bundle (the 17-IRI cascade preserves `rdfs:subClassOf` / `owl:equivalentClass` chains). | OWL reasoner running over the registry. | +| `dolce_marker: DolceMarker` (Endurant / Perdurant / Quality / Abstract) | DOLCE primary category at the per-row philosophical-category tag | `hydrate_dolce` populates DOLCE_V1; downstream `hydrate_owltime` / `provo` / `qudt` / `schemaorg` align via `inherits_from: Some(OGIT::DOLCE_V1.0)` + `dul:isClassifiedBy` / `rdfs:subClassOf dul:Event\|Object\|Quality\|Abstract`. **DolceMarker enum naming open question:** canonical DOLCE+DUL renamed `Endurant → Object` and `Perdurant → Event` in the DUL header — the enum variants either stay on the legacy `Endurant/Perdurant` (with downstream consumers aware of the rename) or migrate to `Object/Event`. Decide before D-UB-3 + reflect in the §4.3 hydrator inventory. | Per-row tagging in `FamilyEntry.dolce_marker`. | +| `wikidata_qid: Option` | Wikidata QID like `Q11190` | NOT YET BUILT — no `hydrate_wikidata` hydrator exists. Adding one is ~50 LOC of `OwlHydrator { g: OGIT::WIKIDATA_V1.0, inherits_from: Some(OGIT::DOLCE_V1.0), … }` glue once a tenant requests Wikidata sync. | Wikidata sync / `sameAs` link generation. | + +The `MetaAnchors` fields are **the read API over what the hydrators built**. The 4-stage `authorize()` doesn't consult them directly — it operates on `OwlIdentity` against the per-family codebook. The cross-walks are consulted when interop with an external system (Foundry export, OWL reasoner, Wikidata sync) is requested. -These are **interop crutches**, not the hot path. The 4-stage `authorize()` never consults them; the per-family codebook is the runtime fast lane. OWL/DOLCE values ride in `FamilyEntry.axiom_blob` for slots that carry semantic obligations (functional / transitive / inverseFunctional / etc., per `GLUE_LAYER_OGIT_TO_OWL_SPEC.md` 1-byte bitfield). +OWL property characteristics (functional / transitive / inverseFunctional / etc., per `GLUE_LAYER_OGIT_TO_OWL_SPEC.md` 1-byte bitfield in `FamilyEntry.owl_characteristics`) flow the same way — `hydrate_dolce` + the L2/L3 hydrators preserve the OWL axioms in `FamilyEntry.axiom_blob`, and the bitfield is the runtime cache over them. ### 4.2 Lance-cache persistence (the "cached in lancedb column" property) @@ -148,9 +189,9 @@ Reads are append-only; writes go through `MappingProposal::sha256()`-keyed dedup **This is what makes the codebook "O(1) cached in lancedb column":** the column store IS the cache; reads are scan-then-build-in-memory once at boot; lookups are array-index on `OwlIdentity.slot()` after. -### 4.3 Hydrator inventory (post-PR #407) +### 4.3 Hydrator inventory (post-PR #407) — the construction tool -The OWL/DOLCE/OGIT cross-walk surface this plan referenced abstractly is now concrete in `lance_graph_ontology::hydrators::*` (PR #407 + the ~11 preceding feature commits). Every hydrator follows the same shape — a free function `hydrate_(registry: &OntologyRegistry) -> Result` that constructs an `OwlHydrator { g: OGIT::.0, version: OGIT::.1, domain_name: "".to_string(), inherits_from: Option, starting_entity_id: 100 }`, calls `.hydrate(ttl_path, registry)` (or `.hydrate_many(&paths, registry)` for multi-file bundles like DOLCE+DUL+extensions), then registers an edge-IRI whitelist via `registry.register_edge_types(g, &EDGE_WHITELIST)`. The return value is the OGIT `G` slot u32 the bundle landed in. +The hydrators that build the OGIT/OWL/DOLCE mapping (per §4.1) are now concrete in `lance_graph_ontology::hydrators::*` (PR #407 + the ~11 preceding feature commits; PR #408 wires the corresponding `NamespaceRegistry::seed_defaults` entries so the OGIT G-slots register at boot). Every hydrator follows the same shape — a free function `hydrate_(registry: &OntologyRegistry) -> Result` that constructs an `OwlHydrator { g: OGIT::.0, version: OGIT::.1, domain_name: "".to_string(), inherits_from: Option, starting_entity_id: 100 }`, calls `.hydrate(ttl_path, registry)` (or `.hydrate_many(&paths, registry)` for multi-file bundles like DOLCE+DUL+extensions), then registers an edge-IRI whitelist via `registry.register_edge_types(g, &EDGE_WHITELIST)`. The return value is the OGIT `G` slot u32 the bundle landed in — i.e., the address of the synthesised mapping in OGIT space. **Generic substrate (the bO-* scaffold every hydrator instantiates):** @@ -223,6 +264,26 @@ use lance_graph_ontology::{ The bridge constructor never sees `OwlHydrator` directly — it sees a hydrated `OntologyRegistry`. The deployment / `main.rs` of each consumer chooses the hydration menu. +### 4.4 The hydrators are the spine that makes inheritance O(1) from family buckets + +The shape that earns the cost-model gains: **`inherits_from` + per-family codebook + family-bucket dense array together make schema, label, and codebook inheritance O(1) at lookup time.** Spelled out: + +| Inheritance flavour | Substrate | Lookup cost | Pre-bake step | +|---|---|---|---| +| **Schema inheritance** (a `Patient` in Healthcare resolves to `dul:Object → dul:Agent → dul:PhysicalAgent` per the DOLCE chain) | Each hydrator's `inherits_from: Option` chains its OGIT G-slot to its parent. `OGIT::DOLCE_V1` is the root (None). At hydration time, `OntologyRegistry` walks the `rdfs:subClassOf` / `owl:equivalentClass` chain ONCE and **flattens** it into `FamilyEntry.axiom_blob`. | O(1) — one masked u16 compare into `OgitFamilyTable[OgitFamily.0 as usize]`, then `.entries[OwlIdentity.slot() as usize]`. **Zero chain-walks at query time.** The flattened chain rides in the static blob. | At hydration: walk the OWL/DOLCE/PROV-O `subClassOf` graph from leaf to root, materialise as `axiom_blob`. Per-family lock-in by `inherits_from`. | +| **Label inheritance** (`rdfs:label@de` / `rdfs:label@en` propagated from parent when child lacks an override) | `hydrate_` reads `rdfs:label` per-locale at TTL parse time. When a slot has no own label, the inherits-from walker copies the parent's label into `FamilyEntry.label_uri` (or `.label_de` / `.label_en` once those columns ride the FamilyEntry — see `lance-cache` schema §4.2). | O(1) — same `OgitFamilyTable` array index → `FamilyEntry.label_*` field read. **Zero parent lookup at query time.** | At hydration: per-locale label collapse during the subClassOf walk. | +| **Codebook inheritance** (per-family centroid reuses when a slot's content distribution overlaps the parent family's) | `PerFamilyCodebook` (§3.3) is sized 5-8 bit per family; when a child family's slot range overlaps the parent's centroid space, the child's `centroid_blob` REFERENCES the parent's centroid index directly (no per-child re-quantisation). The reference is a u8 offset into the parent codebook. | O(1) — `FamilyEntry.centroid_blob` → `parent_family.codebook[centroid_idx]`. **One indirection max.** | At hydration: when a child family's content distribution is statistically close to the parent's (Jirak-aware Berry-Esseen bound per `I-NOISE-FLOOR-JIRAK`), emit a centroid-reference instead of re-fitting. | + +**Why family buckets, not a flat dict:** A flat OGIT URI → entry HashMap costs ~50-100ns per lookup at scale (hash + collision walk + cache miss). The 256-slot dense `[Option; 256]` per family + the high-byte family index makes it **two array indices, both predictable, both L1-cache-resident**: ~5ns. The 20× cost difference is the headline gain from the layered substrate. + +**What this means for downstream consumers:** + +- `woa-bridge` doesn't pay the OWL-reasoning cost at every `wo_detail` query. Whatever schema / label / codebook inheritance applies to `ogit.WorkOrder:Customer` is pre-baked into the `FamilyEntry` at boot; the route handler reads one entry in one array index. +- `medcare-bridge`'s clinical-code lookups (ICD-10 / SNOMED via `hydrate_skos`) inherit their hierarchy chain at hydration; runtime cohort queries walk only the per-row identity, never the standards' subClassOf chain. +- `smb-bridge`'s X-Rechnung validation against ZUGFeRD Schematron rules doesn't re-parse the rule set at every invoice — the rules are baked once at boot via `hydrate_zugferd_rules` into the same family-bucket structure, and the per-invoice check is one masked predicate per rule. + +**The construction tool (hydrators) and the runtime substrate (family buckets) are co-designed:** the hydrators only earn the O(1) property because they flatten the inheritance chains at construction time; the family buckets only earn the cost-model gain because the hydrators populate them with per-slot dense entries. Neither half works alone. + --- ## 5 — Deliverables From 782355073b5693ece0ff5a5b6ae6b9c54c34c221 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:20:32 +0000 Subject: [PATCH 08/22] =?UTF-8?q?docs(plans/unified-bridge):=20=C2=A74.5?= =?UTF-8?q?=20=E2=80=94=20substrate=20framing=20locked=20as=20CAM=20bar=20?= =?UTF-8?q?codes=20(NOT=20random=20bitpacking)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User correction: lance-graph is supposed to think in "bar codes" (CAM content-addressable memory), NOT random bitpacking. The §4 framing through 4.4 risked reading as "OwlIdentity is a u16 with high/low byte slices stuffed in for storage convenience" — which would be random bitpacking. The correct framing is: OwlIdentity IS the bar code; the per-family codebook IS the CAM substrate; the 256-slot dense array IS the CAM addressed by the bar code; hydrators ISSUE bar codes from external standards; the inherits-from chain PROPAGATES content-addressability through the hierarchy. §4.5 NEW — locks the substrate framing: - Side-by-side wrong-vs-right framing table for: OwlIdentity (bar code, not bitpack), FamilyEntry (what bar code resolves to via CAM, not packed beside the u16), codebook (IS the CAM substrate, not quantisation table), 256-slot dense array (IS the per-family CAM, not storage-density optimisation), hydrators (issue bar codes from external standards, not "populate fields"), inherits-from chain (propagates content-addressability, not parent-pointer for reasoning). - Cross-reference to CLAUDE.md I-VSA-IDENTITIES iron rule (2026-04-21): "VSA operates on IDENTITY fingerprints that POINT TO content. Never on content's bitpacked/quantized register itself." Register-loss problem is the same failure mode this section prevents; OwlIdentity is a Vsa16k-shaped identity at u16 scale. - Four CAM tests (per I-VSA-IDENTITIES Test 0..3) applied directly to the OwlIdentity design: register laziness (URI for schema-authoring, bar code for runtime), bundle size (N=1, no VSA superposition on bar codes), role orthogonality (high byte enforces disjoint family spaces), cleanup codebook (per-family PerFamilyCodebook IS the cleanup codebook). - Concrete diagnostic for PR review — anti-pattern examples (manual bit-unpack of owl.0; ad-hoc OwlIdentity::from_parts in business logic; sidecar HashMaps keyed by OwlIdentity that bypass the family codebook) and positive patterns (bridge.entity(name) → CAM-keyed EntityRef; hydrators issue starting_entity_id integers as CAM coordinates, not payloads). - Cross-tier consistency note: the Lance-cache persistence (§4.2) works because the column store IS a CAM at rest. Hot-path CAM and cold-path CAM are the same shape, just different storage tiers — no semantic translation between them. This explains why §4.4's O(1) inheritance property holds: it holds because the substrate IS a CAM, addressed by bar codes. Random bitpacking would forfeit both the O(1) property and the inheritance-propagation property. --- .../unified-bridge-consumer-migration-v1.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.claude/plans/unified-bridge-consumer-migration-v1.md b/.claude/plans/unified-bridge-consumer-migration-v1.md index 054d1556..2b3331ee 100644 --- a/.claude/plans/unified-bridge-consumer-migration-v1.md +++ b/.claude/plans/unified-bridge-consumer-migration-v1.md @@ -284,6 +284,38 @@ The shape that earns the cost-model gains: **`inherits_from` + per-family codebo **The construction tool (hydrators) and the runtime substrate (family buckets) are co-designed:** the hydrators only earn the O(1) property because they flatten the inheritance chains at construction time; the family buckets only earn the cost-model gain because the hydrators populate them with per-slot dense entries. Neither half works alone. +### 4.5 Substrate framing — CAM bar codes, NOT random bitpacking + +**`OwlIdentity` is a bar code, not a bitpack.** This distinction is load-bearing for the §4 design and is the principle that makes the §4.4 O(1) inheritance property hold. Spell it out before any consumer wires a path-dep: + +| Wrong framing (random bitpacking) | Right framing (CAM bar codes) | +|---|---| +| `OwlIdentity(u16)` is a u16 with arbitrary bit fields slicing into `family << 8 \| slot` for storage convenience. | `OwlIdentity(u16)` is a **content-addressable identifier** — a bar code. The high byte (family) and low byte (slot) are coordinate axes in a CAM-addressable space, not random bit positions. | +| `FamilyEntry` is a struct packed beside the u16 because we have to put the data somewhere. | `FamilyEntry` is **what the bar code resolves to via the per-family CAM** — `OgitFamilyTable[family].entries[slot]` IS a CAM lookup, addressed by the bar code. | +| The codebook (5-8 bit centroids per family) is a quantisation table for compression. | The codebook **IS the CAM substrate** — per-family centroids form the content-addressable space the bar code's low byte indexes into. Distance lookups (`table[code_a][code_b]`) are O(1) CAM-style comparisons, not unpacks. | +| The 256-slot dense array is a storage-density optimization. | The 256-slot dense array **IS the per-family CAM**, sized to exactly one byte of address space; the bar code's low byte IS the array index — there is no separate "address translation" step. | +| Hydrators "populate fields" inside FamilyEntry. | Hydrators **assign bar codes** to entries from external standards. `hydrate_dolce` issues bar codes in the `OGIT::DOLCE_V1` namespace; `hydrate_provo` issues bar codes that *inherit* (via `inherits_from`) the DOLCE addressing, so PROV-O bar codes can be CAM-compared against DOLCE bar codes without a translation step. | +| The inherits-from chain is a parent-pointer for OWL reasoning. | The inherits-from chain **propagates content-addressability** through the hierarchy: a child family's codebook can reference the parent's CAM by u8 offset (§4.4 codebook-inheritance row) because the parent's bar code space IS embedded in the child's. | + +**Why this matters operationally:** the `I-VSA-IDENTITIES` iron rule (`lance-graph/CLAUDE.md`, added 2026-04-21) is the workspace-wide statement of this principle: "**VSA operates on IDENTITY fingerprints that POINT TO content. Never on content's bitpacked/quantized register itself.**" The register-loss problem (XOR-bundling CAM-PQ codes destroys the mapping back to their codebook entries) is the same failure mode this section prevents. `OwlIdentity` is a Vsa16k-shaped identity at u16 scale — the bar code addressing content in the per-family codebook. Treating it as bitpacked storage breaks the CAM property and forfeits the O(1) gain. + +**The four CAM tests (per `I-VSA-IDENTITIES` Test 0..3)** apply directly to the `OwlIdentity` design: + +- **Test 0 — register laziness.** `OwlIdentity` has a natural name (the OGIT URI `ogit.:`) AND an enum/index identity (the u16 bar code). The bar code is the right primitive at the runtime layer; the URI is the right primitive at the schema-authoring layer. Both live; both serve. +- **Test 1 — bundle size.** Bar codes don't bundle (no VSA superposition). The §3.10 DataFusion predicate combines bar codes into a single masked compare, but the bar codes themselves stay singular. N=1 per row, well below the √d/4 ≈ 32 bundle ceiling. +- **Test 2 — role orthogonality.** Each family's 256-slot bar-code space is disjoint from every other family's (the high byte enforces it). Within a family, slot allocations are explicit and authored by the hydrator (not collision-prone). +- **Test 3 — cleanup codebook.** The per-family `PerFamilyCodebook` IS the cleanup codebook. After any CAM scan (e.g., similarity search via §11A.1 CAM-PQ), unbind resolves cleanly to the codebook entry. + +**Concrete diagnostic — how to spot the wrong framing in a PR:** + +- ❌ `let family = (owl.0 >> 8) as u8; let slot = (owl.0 & 0xff) as u8; do_something(family, slot)` — treating the bar code as a bitpacked u16 to be unpacked. Wrong layer; should call `owl.family().0` and `owl.slot()` which are the CAM coordinate accessors. +- ❌ `OwlIdentity::from_parts(family, slot)` constructors used freely in business logic. Bar codes should be *issued by hydrators* and *referenced by lookups* — not constructed ad-hoc. +- ❌ Storing `OwlIdentity` content in something other than the per-family codebook (e.g., a sidecar HashMap keyed by `OwlIdentity`). Sidecars defeat the O(1) CAM property; everything lives inline in `FamilyEntry`. +- ✅ `bridge.entity("Customer")?.schema_ptr.entity_type_id()` resolves the public name through the registry to a bar-coded `EntityRef`; downstream code reads via the bar code as a CAM key. +- ✅ Hydrators *issue* bar codes at boot via `OwlHydrator { starting_entity_id: 100, … }` — the integer is the CAM coordinate, not a payload. + +**The bar-code framing also explains why the Lance-cache persistence (§4.2) works:** the Lance dataset column store IS a CAM at rest. The per-row `(owl_id u16, tenant_id u32)` identity is the bar-coded address; the row payload (ciphertext + merkle root) is what the bar code addresses on disk; the boot-time scan reconstructs the in-memory CAM from the on-disk CAM with no semantic translation in between. The hot-path CAM and the cold-path CAM are the same shape — just different storage tiers. + --- ## 5 — Deliverables From 9da549d67b75518cc8b5187b8c9d0353f2077011 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:24:26 +0000 Subject: [PATCH 09/22] =?UTF-8?q?docs(plans/unified-bridge):=20=C2=A74.6?= =?UTF-8?q?=20=E2=80=94=20authoritative=20spine=20is=20read-only;=20caches?= =?UTF-8?q?=20stay=20clean=20only=20because=20writes=20are=20governed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User correction: the OWL maps can be cached in lance-graph + OGIT (the §4.5 CAM substrate makes it cheap), but it would become dirty quickly if everyone could edit the authoritative spine. §4.5 explained why the spine CAN be cached; §4.6 locks in why those caches stay clean — one controlled write path. §4.6 NEW — covers: - The substrate property (§4.5) and the governance property are co-load-bearing: CAM alone yields drift under ad-hoc edits; governance alone forfeits the O(1) gain. Together they yield stable bar codes + clean inheritance chains + fast lookups + trust the spine is what the spine says. - The one controlled write path (ASCII diagram): external standards → producer (hydrator / scanner / admin form) → MappingProposal stream with proposal_sha256 dedup → OntologyRegistry::append_proposals → Lance dataset (cold CAM) → boot-time scan → in-memory OgitFamilyTable (hot CAM) → consumer bridges READ ONLY. - Three properties enforced: single producer surface (MappingProposal is the only mutable DTO); idempotent dedup at boundary (proposal_sha256 keyed); versioned G-slots (OGIT::*_V1.1 carries the version field; V2 ships alongside V1 for controlled migration). - Concrete failure cascade if a consumer edits the spine ad-hoc: bar code reshuffle → cross-consumer cache incoherence → spine collapses to mass of locally-true views that don't intersect cleanly. Multiply by 75 families × 256 slots × N consumers = no spine. - Enforcement surfaces (3 checkpoints, all shipped): source-of-truth TTL in AdaWorldAPI/OGIT (PR-reviewed merges); OntologyRegistry write API only via append_proposals; NamespaceBridge trait surface exposes only read methods. - Legitimate change path: upstream standard ships new version → TTL update in AdaWorldAPI/OGIT (PR review) → new hydrator at OGIT::_V2.0 → consumers opt-in at boot → side-by-side until full migration → V1 deprecated. Same governance pattern as I-LEGACY-API-FEATURE-GATED for CausalEdge64. - Three new TODO items added: · D-UB-12 — lock read-only invariant in trait surface; gate any pub-mutation methods on OntologyRegistry to pub(crate) or #[doc(hidden)]. ~30 LOC + 2 tests. · D-UB-13 — proposal_sha256 idempotency test (re-hydrate same TTL → 0 new rows). ~40 LOC test. · D-UB-14 — versioned G-slot migration smoke test; pin-V1 vs pin-V2 consumers in same registry don't cross-contaminate. ~80 LOC test. This is the runtime/data-layer analogue of the bookkeeping append-only governance (settings.json Edit/Write deny on the 8 board files per the ATT activation). Same discipline, different substrate. --- .../unified-bridge-consumer-migration-v1.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/.claude/plans/unified-bridge-consumer-migration-v1.md b/.claude/plans/unified-bridge-consumer-migration-v1.md index 2b3331ee..ed8eba5a 100644 --- a/.claude/plans/unified-bridge-consumer-migration-v1.md +++ b/.claude/plans/unified-bridge-consumer-migration-v1.md @@ -316,6 +316,97 @@ The shape that earns the cost-model gains: **`inherits_from` + per-family codebo **The bar-code framing also explains why the Lance-cache persistence (§4.2) works:** the Lance dataset column store IS a CAM at rest. The per-row `(owl_id u16, tenant_id u32)` identity is the bar-coded address; the row payload (ciphertext + merkle root) is what the bar code addresses on disk; the boot-time scan reconstructs the in-memory CAM from the on-disk CAM with no semantic translation in between. The hot-path CAM and the cold-path CAM are the same shape — just different storage tiers. +### 4.6 The authoritative spine is read-only — caching only works because writes are governed + +**§4.5 explained why the OGIT/OWL/DOLCE mapping CAN be cached (CAM substrate addressed by bar codes). §4.6 locks in why those caches stay clean: the authoritative spine has exactly one controlled write path. If every consumer (or every session, or every developer) could edit the spine directly, every cache would go dirty within hours — the inherits-from chain plus the per-family codebook plus the bar-code stability assumption all evaporate under uncontrolled mutation.** + +The substrate property and the governance property are co-load-bearing. Either alone fails: + +| Property | Without the other half | +|---|---| +| CAM substrate (§4.5) alone — no governance | Consumers cache; spine drifts under ad-hoc edits; cache invalidation thunders through `inherits_from` chains; bar codes reshuffle when parent codebooks re-fit; every consumer's runtime CAM ends up serving stale or wrong content within days | +| Governance alone — no CAM substrate | Consumers can't cache cheaply; every lookup pays OWL-reasoning cost or chain-walks the rdfs:subClassOf graph at query time; the §4.4 O(1) gain evaporates; the spine being "authoritative" buys nothing because nobody can afford to consult it | + +The two halves together yield the operational property: **stable bar codes** + **clean inheritance chains** + **fast lookups** + **trust that the spine is what the spine says it is**. + +#### The one controlled write path + +``` +External standard ──► Producer (hydrator / scanner / ──► Authoritative spine +(DOLCE / OWL-Time / schema scanner / admin form) (lance-graph-ontology + PROV-O / QUDT / FIBO / │ registry @ OGIT::*_V1 + SKOS / Schematron / XSD / │ emits MappingProposal stream; G-slots; family + SKR DATEV / ZUGFeRD / │ every proposal carries codebooks; edge + future OGIT/NTO/) │ proposal_sha256 for idempotent whitelists) + │ dedup │ + │ │ + └─► OntologyRegistry::append_proposals ─────────┘ + │ + ▼ + Lance dataset under + lance-cache feature + (cold-path CAM at rest) + │ + ▼ + boot-time scan → in-memory + OgitFamilyTable[OgitFamily] + (hot-path CAM) + │ + ▼ + Consumer bridges READ ONLY + (woa-bridge / smb-bridge / + medcare-bridge; each holds + Arc) +``` + +**Three properties this enforces:** + +1. **Single producer surface.** `MappingProposal` is the only DTO that lands in the registry's mutable surface. Hydrators emit them. Schema scanners (future MySQL/MSSQL inventory) emit them. Customer admin forms (future) emit them. Everything funnels through one append path — `OntologyRegistry::append_proposals` — so the write boundary is auditable and rate-controllable. +2. **Idempotent dedup at the boundary.** Every proposal carries `proposal_sha256` (the §4.2 Lance column schema row). Re-submitting the same proposal is a no-op; the row already exists. This makes hydrator re-runs safe (boot, restart, schema refresh) without growing the dataset linearly. +3. **Versioned G-slots.** Every hydrator stamps `OGIT::_V1.1` (the version field on the OGIT tuple) into the bundle. When a hydrator ships a breaking change to its bundle (e.g., DOLCE adopts the canonical `Endurant → Object` rename), the new version lands at `OGIT::DOLCE_V2.0` while `OGIT::DOLCE_V1` stays available. Consumers pin a major version; cache invalidation is a controlled migration, not a silent drift. + +#### Why "everyone editing the spine" is the failure mode the §4.5 CAM property cannot survive + +Concrete failure cascade if a consumer edits the authoritative spine ad-hoc: + +1. Consumer A reassigns the `Customer` bar code in `OGIT::SMB_V1` from slot 17 to slot 23 (because slot 17 "felt wrong"). The reassignment lands directly in the in-memory registry, bypassing `MappingProposal`. +2. Consumer A's cache is now correct. Consumer A's runtime CAM lookups for `Customer` resolve to slot 23 — works fine. +3. Consumer B starts a session, boots from the Lance-cache dataset which still has slot 17 for `Customer` (the on-disk CAM was never updated; the in-memory mutation isn't reflected). Consumer B's cache has slot 17. A's runtime now shares a registry with B's runtime — they disagree on `Customer`'s bar code. +4. Cross-consumer Cypher query (smb-bridge ↔ shared `OGIT::Network` for IP addresses) returns row 17 to A, row 23 to B. Both consumers' CAM is internally consistent but mutually incoherent. +5. Multiply by 75 OGIT families × 256 slots each × ad-hoc edits across N consumers = the spine is no longer a spine. It's a mass of locally-true views that don't intersect cleanly. + +This is exactly the scenario the bookkeeping-file append-only governance (settings.json deny on Edit/Write/MultiEdit for the 8 board files; see `.claude/ATT/ACTIVATION.md` for the activation receipt) prevents at the documentation layer. §4.6 is the runtime / data-layer analogue: the same discipline applied to the OGIT/OWL/DOLCE spine. + +#### Enforcement surfaces + +Three checkpoints prevent the "everyone edits the spine" failure mode: + +| Layer | Mechanism | Status | +|---|---|---| +| Source-of-truth TTL | `AdaWorldAPI/OGIT` GitHub repo with PR-reviewed merges. Hydrators read from a checked-out clone; they never mutate the source. | Shipped (`AdaWorldAPI/OGIT` exists; hydrators read `data/ontologies/*.ttl` relative to workspace root). | +| `OntologyRegistry` write API | Only `append_proposals` mutates the registry. Direct mutation methods don't exist. Per `lance-graph-ontology/src/lib.rs:23` — "everything funnels through one append path." | Shipped per the existing crate surface. | +| Per-consumer bridge | `NamespaceBridge` (the trait every per-consumer bridge implements) exposes only `entity()`, `edge()`, `entity_by_uri()`, `row()` — all read-only. No `set_*` or `mutate_*` method. | Shipped per `lance-graph-ontology::bridge::NamespaceBridge`. | + +The new D-UB-* deliverables in §5 do not introduce new write paths. The per-consumer constructors (D-UB-4..6) take an already-hydrated `Arc` and hand back a `UnifiedBridge` — they cannot mutate the spine even if they wanted to. + +#### When the spine genuinely needs to change + +The legitimate change path goes through external-standard updates: + +1. Upstream standard ships a new version (e.g., DOLCE 2.0, PROV-O update, FIBO quarterly release). +2. TTL file updated in `AdaWorldAPI/OGIT` (PR-reviewed at the source-of-truth repo). +3. New hydrator version (e.g., `hydrate_dolce_v2`) added to `lance-graph-ontology::hydrators`, registered at `OGIT::DOLCE_V2.0`. Old `hydrate_dolce` stays for consumers pinned to `V1`. +4. Consumers opt in to V2 at their own boot time. The Lance-cache dataset carries both versions side-by-side until consumers migrate. +5. Once all consumers report `V2` adoption, `V1` is deprecated upstream and eventually removed. + +This is the same versioned-migration pattern the `I-LEGACY-API-FEATURE-GATED` iron rule prescribes for CausalEdge64 (v1 layout under v2-feature gates with documented no-op + migration pointers). Same governance, different substrate. + +#### Concrete TODO surfaced by this section + +- **D-UB-12 (NEW)** — Lock the read-only invariant in the trait surface. `lance-graph-ontology::bridge::NamespaceBridge` already exposes only read methods, but the `OntologyRegistry` struct has methods that mutate (necessarily — hydrators have to write). Document the boundary: hydrators have `&OntologyRegistry` write access at boot time; consumers receive `Arc` read-only after. Add `#[doc(hidden)]` or `pub(crate)` gates on the registry's mutate-shaped methods if any are currently `pub` and reachable from consumer crates. ~30 LOC + 2 tests asserting consumer crates can't call mutation methods. +- **D-UB-13 (NEW)** — Add the proposal_sha256 idempotency test if not already present. Re-hydrating the same TTL twice should produce zero new rows in the Lance cache dataset. ~40 LOC test, no new code. +- **D-UB-14 (NEW)** — Versioned G-slot migration smoke test. Hydrate `OGIT::DOLCE_V1` + `OGIT::DOLCE_V2` (synthetic V2 in a test fixture) into the same registry; assert consumers pinned to V1 see V1 only, consumers pinned to V2 see V2 only, no cross-contamination. ~80 LOC test, no new code. + --- ## 5 — Deliverables From efbf0ff85f24d7b93a9969f53255bf898364cdbe Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:25:30 +0000 Subject: [PATCH 10/22] =?UTF-8?q?docs(plans/unified-bridge):=20=C2=A74.7?= =?UTF-8?q?=20=E2=80=94=20default=20storage=20rule,=20store=20the=20CAM=20?= =?UTF-8?q?per-OGIT=20(when=20in=20doubt,=20that=20keeps=20things=20tidy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User directive: "if in doubt store the CAM per OGIT so everything is tidy." Locking the default storage rule for any CAM-shaped artifact in the spine. §4.7 NEW — covers: - Per-OGIT-G-slot storage table for: PerFamilyCodebook, OgitFamilyTable.entries[256], edge IRI whitelists, Lance-cache partitioning, hydration-filter namespaces, per-super-domain HKDF context. Each row: where it lives tidily vs where it would live messily if globally pooled. - Six reasons per-OGIT is the tidy default: 1. Cache invalidation stays scoped — one hydrator update = one G-slot rebuild, no blast across the spine. 2. Versioning is local — DOLCE_V1 + DOLCE_V2 coexist physically separate; consumer-pinned migration, not workspace-wide. 3. Hot-path locality matches read patterns — consumer loads only the G-slots it touches; cold G-slots stay on disk. 4. Authorship boundary aligns with cache boundary — hydrate_dolce is sole writer of DOLCE_V1's CAM; per-OGIT IS the lock granularity. 5. Storage layout aligns with the read-only consumer surface — NamespaceBridge.g_lock() already returns the G-slot; consumer working set IS the G-slot CAM. 6. Discovery + governance + caching share the G-slot as one organizational primitive. - Why global storage is wrong: bar-code address space is local (slot 17 in DOLCE ≠ slot 17 in PROV-O); cross-family inheritance via inherits_from is explicit not transitive; updates to one family's codebook would invalidate every other family's bar codes. - Rule of thumb for new §5 deliverables: any CAM-shaped artifact defaults to per-OGIT-G-slot storage. Global storage requires written justification in the deliverable spec. Cross-references: - §4.6 governs WHO writes; §4.7 governs WHERE writes land. - super-domain-rbac-tenancy-v1.md §13.4 hard-lock crypto barrier: per-OGIT storage is the substrate that makes per-super-domain HKDF derivation work at storage-partition time (not row-decryption time). - CLAUDE.md Mandatory Board-Hygiene Rule: per-board-file append-only governance is the doc-layer analogue of per-OGIT CAM storage. Same partitioning instinct. --- .../unified-bridge-consumer-migration-v1.md | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.claude/plans/unified-bridge-consumer-migration-v1.md b/.claude/plans/unified-bridge-consumer-migration-v1.md index ed8eba5a..8adf2a8c 100644 --- a/.claude/plans/unified-bridge-consumer-migration-v1.md +++ b/.claude/plans/unified-bridge-consumer-migration-v1.md @@ -407,6 +407,44 @@ This is the same versioned-migration pattern the `I-LEGACY-API-FEATURE-GATED` ir - **D-UB-13 (NEW)** — Add the proposal_sha256 idempotency test if not already present. Re-hydrating the same TTL twice should produce zero new rows in the Lance cache dataset. ~40 LOC test, no new code. - **D-UB-14 (NEW)** — Versioned G-slot migration smoke test. Hydrate `OGIT::DOLCE_V1` + `OGIT::DOLCE_V2` (synthetic V2 in a test fixture) into the same registry; assert consumers pinned to V1 see V1 only, consumers pinned to V2 see V2 only, no cross-contamination. ~80 LOC test, no new code. +### 4.7 Default storage rule — store the CAM per-OGIT, not globally + +**Rule of thumb: when in doubt about where to put a CAM (codebook, fingerprint table, distance matrix, slot allocator, edge whitelist), store it per-OGIT G-slot. The OGIT G-slot is the natural sharding key; storing per-OGIT keeps everything tidy.** + +Concretely: + +| Artifact | Where it lives (per-OGIT, tidy) | NOT where it lives (global, messy) | +|---|---|---| +| `PerFamilyCodebook` (5-8 bit centroids per family) | One per OGIT G-slot: `OGIT::DOLCE_V1` owns its codebook; `OGIT::PROVO_V1` owns its codebook; `OGIT::HEALTHCARE_V1` (future) owns its codebook | A single workspace-wide centroid table indexed across all families | +| `OgitFamilyTable.entries: [Option; 256]` | One 256-slot array per OGIT G-slot. The slot byte is bar-code-local to that OGIT bundle | A flat `[Option; 65_536]` keyed by the full `OwlIdentity` u16 | +| Edge IRI whitelists (the 17-IRI DOLCE list; the 22-IRI PROV-O list; etc.) | Registered per OGIT G-slot via `OntologyRegistry::register_edge_types(g, &EDGE_WHITELIST)` — already shipped this way | A single workspace-wide allow-list mixing predicates from all standards | +| Lance-cache rows (§4.2) | Partitioned by `(bridge_id, namespace)` — the column-store layout already aligns with per-OGIT storage | A single un-partitioned Lance table scanned linearly | +| Boot-time `OntologyRegistry::hydrate_once_sync(ttl_root, &[namespace])` filter | Caller specifies which namespaces to hydrate; only those G-slots populate | Implicit global hydration of every TTL the registry can find | +| Per-tenant DEK + per-super-domain HKDF context (super-domain-rbac §13.4 hard-lock crypto) | Salt + context derived per-super-domain (effectively per-OGIT-group); rows in different super domains are cryptographically distinct | Shared key material across super domains; hard-lock barrier evaporates | + +**Why this is the tidy default:** + +1. **Cache invalidation stays scoped.** When `hydrate_provo` runs an idempotent update against `OGIT::PROVO_V1`, only that G-slot's CAM rebuilds. DOLCE's CAM doesn't get touched. Healthcare's CAM doesn't get touched. The blast radius of any single hydrator update equals one G-slot. +2. **Versioning is local.** `OGIT::DOLCE_V1` and `OGIT::DOLCE_V2` coexist side-by-side because their CAMs are physically separate. Consumers pin a version; migration is "consumer X moves from V1 to V2", not "the whole workspace migrates." +3. **Hot-path locality matches read patterns.** A consumer that touches `Healthcare` + `DOLCE` + `OWL-Time` loads three G-slot CAMs, not the whole spine. Memory residency is bounded by what the consumer actually queries; cold G-slots stay on disk in the Lance dataset. +4. **Authorship boundary aligns with cache boundary.** `hydrate_dolce` is the sole writer of `OGIT::DOLCE_V1`'s CAM. No shared-write conflicts; no concurrent-hydrator coordination required. Per-OGIT storage IS the lock granularity. +5. **Storage layout aligns with the read-only consumer surface.** `NamespaceBridge.g_lock()` already returns the OGIT G-slot a consumer is locked to (§3.1 in this plan). The consumer's CAM working set IS the G-slot CAM. The storage layout is the read layout. +6. **Discovery + governance + caching all share the OGIT G-slot as the unit.** A new external standard adopts a G-slot; the hydrator writes there; consumers pin and read from there; the Lance dataset partitions on it; invalidation scopes to it; cross-G-slot access requires the `inherits_from` chain explicitly. One organizational primitive, multiple consistent uses. + +**The opposite default (global CAM) is wrong because:** + +- The 256-slot bar-code address space inside any one family is local — slot 17 in DOLCE means something different from slot 17 in PROV-O. Mixing them globally would require a 65,536-slot expansion just to address what 75 × 256 = 19,200 per-family slots cover today. +- Cross-family inheritance via `inherits_from` is **explicit**, not transitive-across-a-global-table. PROV-O's `inherits_from: Some(OGIT::DOLCE_V1.0)` is the controlled cross-reference; a global CAM would erase the discipline and let any consumer chain arbitrary lookups. +- Updates to one family's codebook would invalidate every other family's bar codes (because their slot indices might shift). Per-OGIT storage IS the firewall. + +**Rule of thumb, restated for §5 deliverables:** new D-UB-* deliverables that introduce a new CAM-shaped artifact (centroid table, distance matrix, fingerprint allocator, slot dispatcher, edge whitelist, label cache) default to per-OGIT-G-slot storage unless there's a written reason in the deliverable spec why global storage is preferable. The default ALWAYS keeps things tidy; global storage requires justification. + +#### Cross-reference + +- This complements §4.6 (read-only authoritative spine + controlled write path) — §4.6 governs **who** writes; §4.7 governs **where** the writes land. +- This complements `super-domain-rbac-tenancy-v1.md` §13.4 (hard-lock partner matrix) — per-OGIT storage is the substrate that makes per-super-domain HKDF derivation possible. Without per-OGIT CAMs, cryptographic separation between Healthcare and OSINT would have to happen at row-decryption time instead of at storage-partition time. +- This complements `lance-graph/CLAUDE.md § Mandatory Board-Hygiene Rule` — per-board-file append-only governance is the documentation-layer analogue of per-OGIT CAM storage at the runtime layer. Same partitioning instinct. + --- ## 5 — Deliverables From b2e85aaf256e4cd4ea888f8069b12cf6340210d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:27:35 +0000 Subject: [PATCH 11/22] =?UTF-8?q?docs(plans/medcare-rs):=20=C2=A79=20?= =?UTF-8?q?=E2=80=94=20position=20vs=20sister=20consumer=20plans=20(behind?= =?UTF-8?q?=20on=20UnifiedBridge,=20otherwise=20quickest=20target)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User observation: MedCare-rs is behind on the UnifiedBridge migration but other than that it's the quickest of the three consumer plans to land. §9 locks this ranking. §9 covers: - Work-remaining ranking table across all three consumers (MedCare-rs < smb-office-rs < woa-rs in total work-remaining) with substrate maturity + UnifiedBridge constructor status + net effort per consumer. - Concrete framing of "behind on UnifiedBridge": medcare_unified_bridge constructor doesn't exist (D-UB-6 = D-LGMC-4). Today consumers take &MedcareBridge for resolution + &medcare-rbac for RLS separately; target is state.unified_bridge.authorize(owl, row_tenant, op) as one 4-stage call. SMB has its constructor in-tree (parameterised over OgitBridge placeholder); MedCare doesn't. - Why MedCare is still quickest despite being behind: the scaffolding gap that makes woa-rs Phase 0+1 expensive (~13 days) is already done in MedCare-rs (medcare-bridge crate, vendor symlinks, MedcareRegistry helper, lance-phase2 feature, medcare-rbac sister, LanceProbe parity, parity ingest endpoint, F1-F5 narrative all shipped). Remaining gap is wire-up, not scaffolding: ~570 LOC + ~25 tests across 5 commits. Plausibly a 1-2 day session. - Sequencing recommendation: MedCare-rs is the right target for the "prove the unified-bridge story end-to-end" micro-sprint. Demonstrates parallelbetrieb loop + 4-stage authorize + Lance-cache persistence + cross-language diverse- redundancy witness + HIPAA compliance regime. Suggested PR sequence: D-LGMC-1 → D-LGMC-4 → D-LGMC-2 + D-LGMC-3 → smoke test against LanceProbe ingest. - Phase status revision: D-LGMC-4 is now flagged HEADLINE GAP (was buried in Phase 3 of §2). Critical path remains D-LGMC-1 → D-LGMC-4 → D-LGMC-2/3. This complements §8 (parallelbetrieb shipped + MongoDB alt cold path) by adding the positional framing: §8 says what's already done; §9 says what that means for sequencing. --- .claude/plans/lance-graph-in-medcare-rs-v1.md | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/.claude/plans/lance-graph-in-medcare-rs-v1.md b/.claude/plans/lance-graph-in-medcare-rs-v1.md index daa10d7d..8d3f1106 100644 --- a/.claude/plans/lance-graph-in-medcare-rs-v1.md +++ b/.claude/plans/lance-graph-in-medcare-rs-v1.md @@ -315,3 +315,107 @@ Phases 1-7 of §2 stay; Phase 5 is reduced (D-LGMC-7+8 are shipped, drop them); > Parallelbetrieb infrastructure is already shipped Round-1 (Patient on MySQL ↔ Lance, with the C# ParityWitness, ingest/dashboard endpoints, and 5-phase F1-F5 narrative). This refinement (a) acknowledges what ships, (b) reframes Phase 5 around Round-2 expansion + production wiring + persistent sink, (c) adds Phase 9 to propagate the smb-office-rs three-layer MongoDB shape into medcare-rs as an alternative cold path (alongside, not replacing, MySQL — Iron Rule 1 preserved). + +--- + +## 9 — Position vs sister consumer plans: behind on UnifiedBridge, otherwise the quickest (2026-05-21, same session) + +User observation: **MedCare-rs is behind on the UnifiedBridge migration and probably not yet wired to it — but other than that, it's the quickest of the three consumer plans to land.** Spelling that out: + +### 9.1 Work-remaining ranking across the three consumers + +| Consumer | Substrate maturity | UnifiedBridge constructor | Net work remaining | +|---|---|---|---| +| **MedCare-rs** | **Most mature.** Parallelbetrieb reconciler shipped Round-1 (Patient); `/api/__parity/csharp` ingest + `GET /api/__parity` dashboard endpoints both live; `MedcareRegistry::hydrate(...)` helper shipped; lance-phase2 build wiring shipped (except the one broken call site at `ontology_dto.rs:85`); F1-F5 migration narrative documented; LanceProbe C# parity tool M1 complete; reconciler shell tested with 11 unit tests; MySQL parity-oracle Iron Rule 1 locked. | **Absent.** `medcare_unified_bridge(registry, actor_role, tenant) -> UnifiedBridge` does not exist (D-UB-6 = D-LGMC-4). The consumer is still on the older direct-`OntologyRegistry` + `MedcareBridge::new(...)` shape. | **Smallest.** Once D-LGMC-1 (build fix) + D-LGMC-4 (constructor) land, the remaining work is mostly Round-2 reconciler expansion (D-LGMC-15..18 ~80 LOC each) + LanceProbe-side TODOs (D-LGMC-9..11) + optional MongoDB cold path (D-LGMC-22..26). The headline gap (UnifiedBridge) is ~60 LOC + 4 tests; total Phase-1..4 closure is ~5-7 days. | +| **smb-office-rs** | **Partial.** `smb-bridge` + `smb-ontology` crates shipped (Mongo + Lance EntityStore/EntityWriter impls; auth + RLS features wired against lance-graph-callcenter/-ontology/-rbac). `smb_unified_bridge` constructor EXISTS — but parameterised over `OgitBridge::for_namespace(...)` because `SmbBridge` doesn't exist upstream yet. | **Exists, parameterised over the wrong bridge type.** Type-parameter swap from `UnifiedBridge` to `UnifiedBridge` is a 15-LOC change (D-UB-5 / D-LGSMB-3) once D-UB-2 (SmbBridge skeleton in lance-graph-ontology) ships. | **Medium.** SmbBridge skeleton (3 days upstream) + parameter swap (15 LOC) + TTL authoring for `OGIT/NTO/SMB/` (4 days) + role groups D-SDR-2 expansion (Phase B) + Phase C tenant-type consolidation (2 days). Phase D-E opt-in. Total Phase-A..C: ~9 days. | +| **woa-rs** | **Greenfield.** OGIT TTL vendored at `vendor/ogit/v02-harvest/`; sea-orm + MySQL writer-parity established per DualSink-Pivot 2026-05-15; RFC v02-006 (route codegen) DRAFT; NO `woa-bridge` crate; NO `woa-ontology` crate; NO `lance-graph-*` Cargo dep declared. Only existing lance-graph touch-point is `tests/ontology_cypher_round_trip.rs`. | **Absent.** `woa_unified_bridge(registry, actor_role, tenant)` would land at D-UB-4, but requires Phase 1 of `lance-graph-in-woa-rs-v1.md` (Phase 0 vendor symlink + exclude block + Phase 1 woa-bridge + woa-ontology crates) to complete first. | **Largest.** Phase 0 (1 day mechanical) + Phase 1 (3 days, crate scaffolding mirroring medcare/smb references) + Phase 2 (4 days, route-handler integration) + Phase 3 (5 days, lance-cache + Lance projection) = ~13 days for the equivalent of medcare's already-shipped baseline. Phases 4-5 opt-in. | + +### 9.2 Why MedCare-rs is "behind on UnifiedBridge" + +Concretely, the consumer pattern medcare-rs ships today (per `MedCare-rs/crates/medcare-bridge/src/registry.rs`): + +```rust +// Today's shape — direct registry + direct bridge: +pub struct MedcareRegistry { + pub registry: Arc, + pub bridge: MedcareBridge, +} + +impl MedcareRegistry { + pub fn hydrate(ttl_root: impl AsRef) -> Result { ... } +} +``` + +Consumers (medcare-server, medcare-analytics) take `&MedcareBridge` for ontology resolution and call `registry.resolve(...)` for raw URI lookups. RBAC and tenant isolation are NOT yet composed via `UnifiedBridge::authorize` — they're handled separately by the existing `medcare-rbac` crate's row-level-security registry. There is no single 4-stage authorize call. + +The migration target (per D-UB-6 / D-LGMC-4) is: + +```rust +// Target shape — UnifiedBridge composes registry + RBAC + tenant: +pub fn medcare_unified_bridge( + registry: Arc, + actor_role: &'static str, // physician / nurse / cashier / researcher / hipaa_audit / admin + tenant: TenantId, // mapped from Praxis.id +) -> Result, lance_graph_ontology::Error>; +``` + +Route handlers in medcare-server stop calling `medcare-rbac::rls::check(...)` + `medcare-bridge::registry.resolve(...)` separately and start calling `state.unified_bridge.authorize(owl, row_tenant, op)` (one masked-predicate combine per row, lowered to DataFusion §3.10). The 4-stage flow (chinese-wall → super-domain → role group → slot redaction) becomes one bridge call. + +**What "behind" means** (relative to the sister consumers): smb-office-rs has the `smb_unified_bridge` constructor in tree today (parameterised over OgitBridge as a placeholder); medcare-rs's `medcare_unified_bridge` constructor doesn't exist yet. So MedCare-rs is one constructor-and-call-site-migration behind SMB on the headline migration path. + +### 9.3 Why MedCare-rs is "the quickest" despite being behind + +The substrate gap that makes woa-rs Phase 0 + 1 expensive (vendor symlink + workspace exclude + new crate scaffolding for both `woa-bridge` and `woa-ontology`) is **already done** in MedCare-rs: + +- `crates/medcare-bridge/` exists ✓ +- `vendor/lance-graph/` softlink + `vendor/ndarray/` softlink + Cargo.toml exclude block exists ✓ +- `MedcareRegistry::hydrate(...)` helper exists ✓ +- `MedcareBridge` re-export from lance-graph-ontology exists ✓ +- `lance-phase2` feature flag exists ✓ +- `medcare-rbac` (sister crate, the RLS surface) exists ✓ +- LanceProbe parity tool (the C# diverse-redundancy witness) exists ✓ +- `POST /api/__parity/csharp` ingest endpoint exists ✓ +- 5-phase F1-F5 narrative documented ✓ + +The remaining gap is **wire-up, not scaffolding**. The headline list: + +1. Fix `ontology_dto.rs:85` (~30 LOC). +2. Add `medcare_unified_bridge` constructor (~60 LOC). +3. Close RLS fail-OPEN window for Treatment/Visit/VitalSign (~80 LOC). +4. Round-2 reconciler expansion to Lab/Vital/Diagnosis/Prescription (~320 LOC across D-LGMC-15..18, 80 LOC each). +5. Persistent sink swap from in-process ring → `LanceAuditSink` (~80 LOC). + +Total: ~570 LOC + ~25 tests across 5 commits. **Plausibly a single 1-2 day session once D-LGMC-1 unblocks the build.** Compare: + +- smb-office-rs Phase A-C closure: ~9 days (TTL authoring is the labour-intensive part). +- woa-rs Phase 0-3 closure: ~13 days (greenfield crate work + route-handler integration + Lance projection). + +### 9.4 Sequencing implication + +If a session needs to prove the unified-bridge story end-to-end on a single consumer, **MedCare-rs is the right target**. It demonstrates: + +- The full reconciler loop (MySQL ↔ Lance + drift events ↔ C# parity probe). +- The 4-stage `UnifiedBridge::authorize` flow on a real consumer. +- The full Lance-cache persistence + boot rehydration cycle. +- The full handover protocol against MedCareV2 LanceProbe (the cross-language diverse-redundancy witness). +- HIPAA-style super-domain compliance regime (the most regulated of the three, per `super-domain-rbac §3.7 ComplianceRegime::HIPAA`). + +Recommended micro-sprint shape for the "prove it" session: **D-LGMC-1 → D-LGMC-4 → D-LGMC-2 + D-LGMC-3 → smoke test against the LanceProbe ingest endpoint.** Four PRs, ~200 LOC, one drift-clean window confirms the parallelbetrieb end-to-end. + +### 9.5 Status update on §6 (revised ranking) + +| Phase | Original §6 status | §9 revised | +|---|---|---| +| Phase 1 (D-LGMC-1 — build fix) | Critical path; ~1 day | Unchanged — still the gate | +| Phase 2 (D-LGMC-2/3 — RLS close) | Safety-critical; ~2 days | Unchanged | +| Phase 3 (D-LGMC-4 — UnifiedBridge constructor) | ~1 day | **HEADLINE GAP** — this is the "MedCare is behind on UnifiedBridge" item the user surfaced. Sequenced after D-LGMC-1, blocking. | +| Phase 4 (D-LGMC-5/6 — MUL MEDICAL + context_id) | ~3 days | Unchanged | +| Phase 5 (D-LGMC-9/10/11 — DTO docs + TripleDES + telemetry) | ~4 days, some scaffolding partial | Mostly unchanged; D-LGMC-7/8 SHIPPED per §8.3 | +| Phase 5b (D-LGMC-15..21 — Round-2 + production wiring + persistent sink) | ~5 days | Unchanged | +| Phase 6 (D-LGMC-12/13 — Cypher/SPARQL) | ~5 days opt-in | Unchanged | +| Phase 7 (D-LGMC-14 — CAM-PQ similarity) | ~3 days opt-in | Unchanged | +| Phase 9 (D-LGMC-22..26 — MongoDB alt cold path) | ~6-8 days | Unchanged | + +### 9.6 One-line summary + +> MedCare-rs is behind on the UnifiedBridge migration itself (no `medcare_unified_bridge` constructor) but ahead on substrate (parallelbetrieb shipped + ingest + dashboard + MedcareRegistry + F1-F5 narrative + LanceProbe parity + lance-phase2 wiring all live). Net: smallest total remaining work of the three consumers, biggest single point-blocker (D-LGMC-1 build fix → D-LGMC-4 constructor). Best target for the "prove the unified-bridge story end-to-end" micro-sprint. From 5f813b8aaede4ba273c5c5f52e882a7947c65d7f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:28:48 +0000 Subject: [PATCH 12/22] =?UTF-8?q?docs(plans/smb-office-rs):=20=C2=A77=20?= =?UTF-8?q?=E2=80=94=20empty=20system=20+=20canonical=20UnifiedBridge=20te?= =?UTF-8?q?mplate=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User correction: smb-office-rs is empty (no live business data; the inherited C# WinForms ERP runs in prod, smb-office-rs is the pre-prod Rust refactor) AND already carries the unified-bridge template; that's a different "quickest" than MedCare-rs's "quickest target for end-to-end proof." §7 NEW — covers: - The empty-system property (3 consequences): 1. No business data to migrate (smb-mongo::MongoImporter is one-shot; not witnessing live writes). 2. No reconciler imperative — SmbMongoReconciler exists as the SISTER template for medcare's MysqlReconciler, but no live parallelbetrieb runs until C# WinForms cutover. 3. No live-system constraint on D-LGSMB-3 type-param swap (free 15-LOC change; no production callers to ripple through). - The UnifiedBridge template property: smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge is THE canonical reference (the only existing _unified_bridge function in any consumer crate today). Referenced by unified-bridge-consumer-migration-v1 §3 as "the working reference"; by lance-graph-in-medcare-rs-v1 D-LGMC-4 as the mirror source; by lance-graph-in-woa-rs-v1 D-WLG-3 as the template. The doc-comments on lines 9-14 + 16-25 + 27-33 are the forward-looking design map other consumers absorb. - Three different "quickest" stories spelled out: · MedCare-rs: quickest TARGET for end-to-end proof (mature substrate, ~570 LOC wire-up gap) · smb-office-rs: quickest SOURCE for template harvesting (empty system, canonical reference constructor) · woa-rs: slowest but clearest scope (greenfield, mechanical mirror-from-templates, ~13 days Phase 0-3) - Harvest order diagram: smb-office-rs ships SmbBridge upstream + role groups + type-param swap → MedCare-rs and woa-rs copy by mirroring smb_unified_bridge. MedCare-rs first (substrate exists to plug into), woa-rs after (needs Phase 0+1 scaffolding first). - Status reframing: §1 was right about smb-office-rs being "most-progressed consumer outside MedCare-rs" at the bridge-substrate level; §7 clarifies the business-data level (empty there). §2's Phase A-E deliverables unchanged; §7 adds the propagation context (D-LGSMB-3 unlocks the template harvest, not just smb's own integration). --- .../plans/lance-graph-in-smb-office-rs-v1.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/.claude/plans/lance-graph-in-smb-office-rs-v1.md b/.claude/plans/lance-graph-in-smb-office-rs-v1.md index 39f7779b..81a122b6 100644 --- a/.claude/plans/lance-graph-in-smb-office-rs-v1.md +++ b/.claude/plans/lance-graph-in-smb-office-rs-v1.md @@ -89,3 +89,81 @@ This plan owns those five items. > Promote `smb_unified_bridge` from `UnifiedBridge` to `UnifiedBridge` (the doc-comments promise this swap), ship SMB-shaped role groups (`tax_clerk` / `partner` / `client_user` / `audit_observer`) per D-SDR-2, author `OGIT/NTO/SMB/` TTL upstream, and optionally light up the Cypher + CAM-PQ surfaces over the existing Lance projection. Smallest delta of the three consumer plans; most of the substrate already ships. + +--- + +## 7 — Position vs sister consumer plans: empty + UnifiedBridge template source (2026-05-21, same session) + +User correction: **smb-office-rs is effectively empty + already carries the UnifiedBridge template; that's a different "quickest" than MedCare-rs's "quickest target for end-to-end proof." smb-office-rs is the quickest *template source*, not a target for behavioural migration.** Locking the framing here. + +### 7.1 The empty-system property + +smb-office-rs is pre-production. The inherited C# WinForms ERP (`AdaWorldAPI/SMB-Office/1x1-prg`) is what currently runs in customer deployments; smb-office-rs is the Rust refactor that hasn't replaced it yet. Three concrete consequences: + +1. **No business data to migrate.** Unlike MedCare-rs (which has 104 MySQL tables to reconcile against, with a parity reconciler Round-1 already shipped) and woa-rs (which has live Python WoA on MySQL across Stefan's deployment), smb-office-rs has no production data flowing through it today. The MongoDB connector (`smb-mongo::MongoImporter`) reads the legacy C# BSON schema as a one-shot migration source; it doesn't witness live writes. +2. **No reconciler imperative.** smb-office-rs's `SmbMongoReconciler` (`smb-bridge/src/mongo_reconciler.rs`, 395 LOC, Round-1 = Customer only) exists as the SISTER pattern to medcare-rs's `MedcareMysqlReconciler` — i.e., it's the **template** for the cross-source comparison, but there's no production parallelbetrieb running against it because there's nothing to compare. Once the C# WinForms cutover happens, the reconciler witnesses the dual-write window; until then, it stays in its dormant test-shape state. +3. **No live-system constraint on type-parameter swaps.** D-LGSMB-3 (swap `UnifiedBridge` → `UnifiedBridge`) is a free 15-LOC change because no production callers exist yet. Compare medcare-rs, where the equivalent migration (D-LGMC-4 adding the constructor at all) touches the live medcare-server route handlers — same gesture, more callsite ripple. + +### 7.2 The UnifiedBridge template property + +`smb-office-rs/crates/smb-bridge/src/unified_bridge_wiring.rs::smb_unified_bridge` is **THE canonical reference** for the `_unified_bridge(...)` constructor pattern across all three consumer plans. It is: + +- The 90-LOC reference file (the only existing `_unified_bridge` function in any consumer crate today). +- The artifact `unified-bridge-consumer-migration-v1.md` §3 names as the "working reference." +- The artifact `lance-graph-in-medcare-rs-v1.md` D-LGMC-4 says to mirror when adding `medcare_unified_bridge`. +- The artifact `lance-graph-in-woa-rs-v1.md` D-WLG-3 names as the template for the future `woa_unified_bridge`. +- The doc-comments on lines 9-14 ("the dedicated `SmbBridge` … is not yet in `lance-graph-ontology::bridges`; once it lands the constructor here swaps the type parameter — call sites stay unchanged") + lines 16-25 (rich `auth::TenantId` ↔ transparent `callcenter::TenantId` consolidation) + lines 27-33 (post-D-SDR-2/3 shrink) are the **forward-looking design map** other consumers' constructors absorb. + +In short: smb-office-rs is the place where the unified-bridge pattern was first concretised; the other consumer plans copy from here. + +### 7.3 "Quickest" — but for template harvesting, not behavioural migration + +The three consumers are all in some sense "the quickest," but for different reasons: + +| Consumer | Quickest in this sense | Why | +|---|---|---| +| **MedCare-rs** | Quickest **target** for "prove the UnifiedBridge end-to-end" micro-sprint | Most-mature substrate (parallelbetrieb shipped + ingest + dashboard + F1-F5); smallest wire-up gap (~570 LOC across 5 commits to close Phase 1-4). The bridge demonstrates the full 4-stage authorize against real clinical data + HIPAA compliance regime + diverse-redundancy LanceProbe witness. | +| **smb-office-rs** | Quickest **source** for template harvesting | Empty system (no live data to migrate); already carries the canonical `smb_unified_bridge` reference constructor + the SmbMongoReconciler sister pattern. Other consumers copy from here. The work is mostly *extracting + propagating* the template, not migrating away from a legacy system. | +| **woa-rs** | Slowest, but **clearest scope** (greenfield) | Zero baseline means no migration / no backward-compat / no behavioural-parity constraints from prior Rust state. Phase 0 + 1 are mechanical mirror-from-templates. Cost is volume (~13 days for Phase 0-3 vs ~5-7 days for MedCare's Phase 1-4 closure), not complexity. | + +### 7.4 Implication for sequencing across all three consumer plans + +The harvest order is: + +``` + ┌─────────────────────────────────────────────────┐ + │ smb-office-rs (THIS PLAN) │ + │ — Phase A ships SmbBridge upstream │ + │ — Phase B authors OGIT/NTO/SMB/ TTL + role │ + │ groups (D-SDR-2 expansion) │ + │ — D-LGSMB-3 type-param swap (free, 15 LOC) │ + │ — Phase C tenant-type consolidation │ + │ → smb_unified_bridge is now the locked │ + │ UnifiedBridge reference │ + └────────────────────┬────────────────────────────┘ + │ template harvest + ┌──────────────────┴──────────────────┐ + │ │ + ▼ ▼ + ┌───────────────────────────────┐ ┌───────────────────────────────┐ + │ MedCare-rs │ │ woa-rs │ + │ — D-LGMC-4 add medcare_ │ │ — D-WLG-3 add woa_ │ + │ unified_bridge by mirroring│ │ unified_bridge by mirroring│ + │ smb_unified_bridge │ │ smb_unified_bridge │ + │ — Plug into existing │ │ — Plug into NEW woa-bridge │ + │ medcare-server route │ │ crate (no existing routes) │ + │ handlers │ │ │ + └───────────────────────────────┘ └───────────────────────────────┘ +``` + +smb-office-rs being "empty + template-bearing" makes it the natural source for the propagation. MedCare-rs and woa-rs harvest from it; MedCare-rs first (because it has the substrate to plug into immediately), woa-rs after (because woa-rs's Phase 0 + 1 has to happen before there's anywhere to plug into). + +### 7.5 Status note on this plan's deliverable framing + +§2 of this plan still describes Phase A-E as "smb-office-rs's own work." That's accurate — those phases ship smb-office-rs's UnifiedBridge migration. The §7 reframe doesn't change those deliverables; it adds the **propagation context**: D-LGSMB-3's type-param swap unlocks the template harvest, not just smb-office-rs's own integration. + +The earlier §1 framing — "smb-office-rs is the most-progressed consumer of the lance-graph spine outside MedCare-rs" — is still true at the **bridge-substrate** level (smb-bridge + smb-ontology + auth + rls features shipped). §7 clarifies the **business-data** level: smb-office-rs is empty there. + +### 7.6 One-line summary + +> smb-office-rs is empty (no live business data) + already carries the canonical `smb_unified_bridge` reference constructor + the SmbMongoReconciler sister pattern. Quickest in the **template harvester** sense; MedCare-rs is quickest in the **end-to-end proof target** sense. Harvest order: smb-office-rs ships SmbBridge upstream + role groups + the type-param swap, then MedCare-rs and woa-rs copy the template by mirroring `smb_unified_bridge`. From fe19d321278a1590ce76443794d24d24763eca25 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:30:45 +0000 Subject: [PATCH 13/22] =?UTF-8?q?docs(plans/woa-rs):=20=C2=A710=20?= =?UTF-8?q?=E2=80=94=20ontology-virgin=20at=20the=20hot=20path,=20but=20OG?= =?UTF-8?q?IT=20already=20in=20sea-orm=20at=20the=20cold=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User correction: woa-rs is ontology-virgin at the lance-graph-ontology hot path BUT it already has OGIT baked into sea-orm entities at the cold path. The MedCare-rs MysqlReconciler pattern transfers 1:1 with sea-orm fetchers because sea-orm is ergonomic and self-explaining. §10 NEW — covers: - Cold-path inventory: vendor/ogit/v02-harvest/entities/*.ttl is the source the existing sea-orm entities were generated/hand-mirrored from. Customer / WorkOrder / Position / Tenant / User / Mahnung / Logbook / Dokument / Einsatz / Stundenzettel-Eintrag entities all derive from OGIT TTL per RFC v02-006's layer table. - "Ontology virgin" framing corrected: true at the HOT path (no woa-bridge crate, no OntologyRegistry consumption, no UnifiedBridge constructor); FALSE at the cold path (sea-orm entities are the OGIT structure already in tree, just not codegen'd yet). - MedCare-rs MysqlReconciler pattern transfer: PatientFetcher trait + canned-row testing pattern transfers directly with sea-orm find_by_id substituted for medcare_db::queries::*. Reconciler shell (parse_route + diff_rows + build_event) verbatim from MedCare-rs. - Two-axis harvest order updated: · smb-office-rs → bridge-wiring template (smb_unified_bridge reference) · MedCare-rs → reconciler-pattern (MysqlReconciler shell) woa-rs absorbs both axes; the sea-orm cold path it already has makes the MedCare-rs axis particularly clean to copy. - Phase 0-3 closure revised from ~13 days to ~9-10 days: Phase 1 drops from 3 days to 2 days (sea-orm authoring already done; just helpers + constructor + ontology declarations). Phase 2 drops from 4 to 3 days (fewer initial handlers since RFC v02-006 codegen will absorb most route-shape work). Phase 3 drops from 5 to 3-4 days (D-WLG-9 Lance projection IS the WoaMysqlReconciler). - Three new deliverables added: · D-WLG-15 — WoaMysqlReconciler mirroring MedcareMysqlReconciler 1:1, Round-1 = Customer (/api/customers/{kdnr}), ~80 LOC + 11 tests · D-WLG-16 — Production query-handle wiring with sea-orm find_by_id, ~50 LOC + 2 tests · D-WLG-17 — Drift-event dashboard endpoint (no C# parity tool for woa; sink directly to LanceAuditSink), ~40 LOC + 2 tests - Mirror source for woa-rs is MedCare-rs (sea-orm + MySQL homologous to medcare_db + MySQL), NOT smb-office-rs (MongoDB + BSON). smb-office-rs is the template SOURCE for bridge-wiring; for the storage-tier MIRROR, MedCare is the right reference. --- .claude/plans/lance-graph-in-woa-rs-v1.md | 126 ++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/.claude/plans/lance-graph-in-woa-rs-v1.md b/.claude/plans/lance-graph-in-woa-rs-v1.md index 592f6ca0..3b004406 100644 --- a/.claude/plans/lance-graph-in-woa-rs-v1.md +++ b/.claude/plans/lance-graph-in-woa-rs-v1.md @@ -224,3 +224,129 @@ D-WLG-9 in §3 was framed as "Lance-side projection of WoA's MySQL tables ... wo > lance-graph-ontology is the hot path (resolution + RBAC + label + similarity + Cypher in O(1) or O(log n) over the codebook + Lance projection); sea-orm + MySQL is the cold path (writer-parity authoritative; byte-exact row values; the projection rebuilds from MySQL on drift). The 2026-05-15 DualSink-Pivot writer-parity contract is preserved; Lance is a READ replica, not a third writer. + +--- + +## 10 — Ontology-virgin at the hot path, but OGIT already in sea-orm at the cold path (2026-05-21, same session) + +User correction: **woa-rs is ontology-virgin at the lance-graph-ontology hot path, BUT it already has the OGIT ontology baked into sea-orm entities at the cold path.** This collapses the work-remaining estimate significantly compared to a true greenfield port. + +### 10.1 What's already in tree at the cold-path DTO layer + +`vendor/ogit/v02-harvest/entities/*.ttl` is not just vendored TTL sitting unused. It's the **source the existing sea-orm entities were generated/hand-mirrored from**. Per `woa-rs/Cargo.toml` workspace structure + `RFC v02-006` (route codegen + ontology unification, DRAFT): + +| Layer | woa-rs status today | +|---|---| +| **Source-of-truth TTL** | `vendor/ogit/v02-harvest/entities/*.ttl` + `vendor/ogit/v02-harvest/POLICY.md` (the divergence ledger) | +| **Sea-orm entities** | Customer / WorkOrder / Position / Tenant / User / Mahnung / Logbook / Dokument / Einsatz / Stundenzettel-Eintrag etc. — all derived from the TTL, hand-edited where Python source diverged. RFC v02-006 §"Ontology unification" §"Layer table" makes the mapping explicit. | +| **MySQL schema** | sea-orm migrations carry the column shapes. DualSink-Pivot 2026-05-15 locks Python+Rust both writing the same MySQL. | +| **Wire DTOs** | `src/dto/*.rs` — hand-written today; future codegen from TTL per RFC v02-006 Phase 5. | +| **lance-graph-ontology hot-path** | NOT WIRED — no `woa-bridge` crate, no `OntologyRegistry` consumption, no `UnifiedBridge` constructor. | + +**The "ontology virgin" framing is correct at the hot path only.** At the cold path (sea-orm + MySQL writer-parity), the OGIT ontology IS already structurally present — each sea-orm entity name + its property set + its German/English wire labels (per `ogit:label` / `rdfs:label`) maps to the TTL's `ogit.WorkOrder:*` URIs. The mapping is a hand-mirroring today (per RFC v02-006 layer-by-layer status), not a codegen output yet, but the shape is established. + +### 10.2 The MedCare-rs reconciler pattern transfers directly + +`MedcareMysqlReconciler` (`MedCare-rs/crates/medcare-analytics/src/mysql_reconciler.rs`, 461 LOC, Round-1 = Patient) uses pluggable `PatientFetcher` closures so the production wiring is one config-site away: + +```rust +// medcare-rs production pattern (what's in tree): +pub trait PatientFetcher { + fn fetch_mysql(&self, id: u64) -> Option; + fn fetch_lance(&self, id: u64) -> Option; +} +// Production impl wraps medcare_db::queries::patient::get_patient(...) +``` + +**woa-rs gets the same shape for free** because sea-orm is ergonomic and self-explanatory. The Rust port is: + +```rust +// woa-rs production pattern (to write): +pub trait CustomerFetcher { + fn fetch_mysql(&self, kdnr: i32) -> Option; + fn fetch_lance(&self, kdnr: i32) -> Option; +} + +// Production impl wraps sea-orm: +impl CustomerFetcher for &DbConnection { + fn fetch_mysql(&self, kdnr: i32) -> Option { + // sea-orm find_by_id is ~3 lines; tokio block_on at the trait boundary + // or change the trait to async fn (preferred) + let row = customer::Entity::find_by_id(kdnr).one(self).await.ok().flatten()?; + Some(CanonicalCustomerRow { + kdnr: row.kdnr, + firma: row.firma.unwrap_or_default(), + // ... 7 more fields the OGIT ontology declares for Customer + }) + } + // fetch_lance wraps the WoaConnector::find_by_id over the Lance projection +} +``` + +The reconciler shell (the `parse__route` + `diff__rows` + `build_event` machinery) is verbatim from MedCare-rs. Only the `CanonicalRow` types and the `Fetcher` traits are per-consumer. + +### 10.3 Phase 1 revision — woa-bridge is not greenfield, it's a MedCare-rs port + +§3 Phase 1 (`D-WLG-3` woa-bridge skeleton, `D-WLG-4` woa-ontology, ~200 + 250 LOC) was framed as mirroring MedCare-rs and smb-office-rs. The §10 reframe sharpens this: **the mirror is MedCare-rs specifically**, not smb-office-rs. Reasoning: + +| Aspect | MedCare-rs (mirror source for woa-rs) | smb-office-rs | +|---|---|---| +| Cold-path store | MySQL via sea-orm | MongoDB via BSON wire shape | +| Cold-path fetcher | `medcare_db::queries::*` (sea-orm-shaped) | `MongoConnector::scan(...)` (BSON document iter) | +| Reconciler | `MedcareMysqlReconciler` (MySQL ↔ Lance) | `SmbMongoReconciler` (Mongo ↔ Lance) | +| Bridge constructor | `medcare_unified_bridge` (TODO, D-LGMC-4) | `smb_unified_bridge` (shipped, parameterised over OgitBridge) | +| Lance-cache wiring | `MedcareRegistry::hydrate_with_report(ttl_root)` returns registry + bridge + HydrationReport | `smb-bridge[lance]` feature gates the LanceConnector | + +woa-rs's sea-orm + MySQL shape is **homologous to MedCare-rs's medcare_db + MySQL shape.** The reconciler + constructor + registry-helper all mirror MedCare's structure with sea-orm queries substituted for `medcare_db::queries::*`. Smb-office-rs is the template SOURCE for the bridge-wiring pattern (per `lance-graph-in-smb-office-rs-v1.md` §7), but for the storage-tier MIRROR, MedCare-rs is the right reference. + +### 10.4 Revised effort estimate + +| Phase | Original §3 estimate | §10 revised | +|---|---|---| +| Phase 0 (vendor + exclude) | 1 day mechanical | **Unchanged** — 1 day. | +| Phase 1 (woa-bridge + woa-ontology) | 3 days, "mirroring MedCare-rs and smb-office-rs references" | **2 days revised.** The sea-orm entity authoring is already done; Phase 1 is `WoaRegistry::hydrate()` helper (~50 LOC mirror of `MedcareRegistry`) + `woa_unified_bridge` constructor (~50 LOC mirror of `smb_unified_bridge` with `WoaBridge` type param) + `woa-ontology` declarations crate (~100 LOC; smaller than MedCare's because the entity shapes already exist in sea-orm, so the ontology crate is mostly the SemanticType + Marking + ObjectView annotations, not the entity declarations themselves). Total ~200 LOC + 4 tests. | +| Phase 2 (route-handler integration) | 4 days, "labour-intensive" | **3 days revised.** `OntologyState` extension + Mandant↔TenantId mapping + permission↔actor_role mapping (~170 LOC + 8 tests). Fewer handlers than original framing because RFC v02-006 codegen (when it lands) will absorb most route-shape work; the manual integration is just for the routes touching the unified bridge surface (~6-8 handlers initially). | +| Phase 3 (lance-cache + Lance projection) | 5 days | **3-4 days revised.** D-WLG-8 (`lance-cache` feature, ~100 LOC + 4 tests) unchanged. D-WLG-9 (Lance-side projection of MySQL tables) IS the WoaMysqlReconciler — NEW deliverable below, see §10.5. D-WLG-10 (RLS via `OntologyRegistry::enumerate("WorkOrder")`) unchanged. | +| Phase 4 + 5 (Cypher / SPARQL, CAM-PQ) | 10 + 5 days opt-in | **Unchanged** — opt-in adopts the planner + CAM-PQ surfaces. | + +**Phase 0-3 closure revised from ~13 days to ~9-10 days** with the sea-orm OGIT shortcut + MedCare-rs reconciler pattern transfer. Phases 4-5 stay opt-in (~15 additional days if both ship). + +### 10.5 New deliverable — WoaMysqlReconciler + +- **D-WLG-15 (NEW)** — `woa-rs/crates/woa-bridge/src/mysql_reconciler.rs::WoaMysqlReconciler` mirroring `medcare-analytics::mysql_reconciler::MedcareMysqlReconciler` 1:1 with `CanonicalCustomerRow` (8-10 fields per `vendor/ogit/v02-harvest/entities/Customer.ttl`) substituted for `CanonicalPatientRow`. Same `Reconciler` trait impl, same `DriftKind` (Match / ValueDrift / ShapeDrift / MissingMysql / MissingLance), same pluggable-fetcher pattern for unit testing. Round-1 scope: `/api/customers/{kdnr}` (the WoA Kunden detail route). ~80 LOC + 11 tests (mirroring the medcare-rs reconciler test suite verbatim). **Round-2 expansion (WorkOrder / Position / Mahnung / Tenant) lands as separate ~80 LOC + 5 test PRs on the same shell.** +- **D-WLG-16 (NEW)** — Production query-handle wiring for `WoaMysqlReconciler`'s `CustomerFetcher`: wraps `customer::Entity::find_by_id(kdnr).one(&db).await` for the MySQL side + the corresponding Lance read via `WoaConnector::find_by_id(kdnr)` for the SPO/Lance side. ~50 LOC + 2 integration tests against a real MySQL fixture + Lance dataset (gated behind `--features mysql-integration lance-phase2`). +- **D-WLG-17 (NEW)** — `POST /api/__parity/csharp` (or equivalent — there is no C# parity tool for woa-rs yet) — actually NOT needed; woa-rs has no diverse-redundancy client like MedCareV2 LanceProbe. The reconciler's drift events sink directly to the persistent `LanceAuditSink` (or in-process ring buffer until D-LGMC-21 lands LanceAuditSink upstream). ~40 LOC + 2 tests for the drift-event dashboard endpoint `GET /api/__parity`. + +### 10.6 Implication for the cross-consumer harvest order (per smb-office-rs §7.4) + +Updated harvest diagram: + +``` + ┌─────────────────────────────────────────────────┐ + │ smb-office-rs │ + │ ships UnifiedBridge wiring template │ + │ (smb_unified_bridge as the reference) │ + └────────────────────┬────────────────────────────┘ + │ pattern harvest + ┌──────────────────┴──────────────────┐ + │ │ + ▼ ▼ + ┌───────────────────────────────┐ ┌───────────────────────────────┐ + │ MedCare-rs │ │ woa-rs │ + │ has: parallelbetrieb shipped │ │ has: OGIT in sea-orm; cold │ + │ + MysqlReconciler shell │ │ path entities established│ + │ needs: medcare_unified_bridge│ │ needs: woa-bridge crate + │ + │ constructor + Phase 5b│ │ WoaMysqlReconciler │ + │ Round-2 expansion │ │ (mirror MedCare │ + │ │ │ MysqlReconciler 1:1)│ + └────────────────┬───────────────┘ └───────────────────────────────┘ + │ ▲ + │ reconciler-pattern harvest │ + └─────────────────────────────────────┘ +``` + +**Two-axis harvest:** smb-office-rs → bridge-wiring template (`_unified_bridge`); MedCare-rs → reconciler-pattern (`MysqlReconciler` + cross-source diff). woa-rs absorbs both axes; the sea-orm cold-path it already has makes the MedCare-rs axis particularly clean to copy. + +### 10.7 One-line summary + +> woa-rs is ontology-virgin at the lance-graph-ontology hot path BUT already has OGIT baked into sea-orm entities at the cold path; the MedCare-rs MysqlReconciler pattern transfers 1:1 with sea-orm fetchers (sea-orm ergonomics + self-explaining API make this cheap). Phase 0-3 closure revised from ~13 to ~9-10 days; new D-WLG-15..17 add WoaMysqlReconciler + production wiring + drift dashboard mirroring the MedCare-rs shape. From ca61816915bfc117a4e57e220f3fcb6aaa2558e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:35:00 +0000 Subject: [PATCH 14/22] =?UTF-8?q?docs(plans/woa-rs):=20=C2=A711=20?= =?UTF-8?q?=E2=80=94=20the=20rewarding=20path,=20woa-rs=20as=20integration?= =?UTF-8?q?=20target=20harvesting=20XRechnung=20+=20parallelbetrieb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User strategic reframe: MedCare-rs and smb-office-rs are transcodes that never worked as Rust (pre-prod Rust ports of C# WinForms apps whose UIs can't move to web cheaply). woa-rs is different — it's a web application transcode (Python/Flask → Rust/axum), so the same engineering work produces VISIBLE WEB-UI feedback per PR instead of test-report-only changes. That asymmetry makes woa-rs the rewarding integration target even though §10 named MedCare-rs the quickest substrate- complete target. Engineering completeness ≠ user-visible reward. §11 NEW — covers: - The transcode-vs-webapp asymmetry: woa = minutes-per-PR visible feedback; SMB + MedCare = days-to-weeks (customer surface is C# WinForms outside the port's scope). - Three-harvest diagram: HARVEST 1 from smb-office-rs (hydrate_zugferd + hydrate_zugferd_rules + SchematronHydrator + XsdHydrator → woa-rs XRechnung/ Factur-X invoice generation, visible as downloadable EN16931-conformant XML+PDF); HARVEST 2 from MedCare-rs (MedcareMysqlReconciler shell + parallelbetrieb trait → WoaMysqlReconciler with sea-orm fetchers + /api/__parity drift dashboard); HARVEST 3 from in-tree (vendor/ogit + sea-orm entities + lance-graph-ontology hydrators → /api/__ontology + /api/__graph routes). - 9-rung PR ladder where each rung produces a screenshot: PR-1/2 scaffolding (build green); PR-3/4 ontology hydration (/api/v1/health + /api/__ontology visible); PR-5 XRechnung visible reward (Stefan downloads EN16931 invoice); PR-6 parity dashboard visible reward (admin sees drift status); PR-7 tenant RLS (cross-tenant 404 visible); PR-8 Cypher playground (opt-in); PR-9 similarity (opt-in). - Resolution of the three "right target" framings across the plans: smb is right for the bridge-wiring TEMPLATE + XRechnung flow; MedCare is right for the reconciler SHELL + parity-dashboard pattern; woa is right for the CUSTOMER-VISIBLE INTEGRATION. Different questions, three right answers, not a linear ranking. - Harvest shape correction: NOT linear (smb → MedCare → woa); it's fan-IN to woa. SMB and MedCare ship patterns in parallel; woa absorbs both + adds the web-UI surface. - What stays unchanged: §9 hot/cold split + §10 effort estimate (~9-10 days Phase 0-3) + D-WLG-1..17 deliverable IDs. §11 specifies SHIPPING ORDER by visible-reward, not per-PR cost. --- .claude/plans/lance-graph-in-woa-rs-v1.md | 109 ++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/.claude/plans/lance-graph-in-woa-rs-v1.md b/.claude/plans/lance-graph-in-woa-rs-v1.md index 3b004406..6be4da0c 100644 --- a/.claude/plans/lance-graph-in-woa-rs-v1.md +++ b/.claude/plans/lance-graph-in-woa-rs-v1.md @@ -350,3 +350,112 @@ Updated harvest diagram: ### 10.7 One-line summary > woa-rs is ontology-virgin at the lance-graph-ontology hot path BUT already has OGIT baked into sea-orm entities at the cold path; the MedCare-rs MysqlReconciler pattern transfers 1:1 with sea-orm fetchers (sea-orm ergonomics + self-explaining API make this cheap). Phase 0-3 closure revised from ~13 to ~9-10 days; new D-WLG-15..17 add WoaMysqlReconciler + production wiring + drift dashboard mirroring the MedCare-rs shape. + +--- + +## 11 — The rewarding path: woa-rs as integration target, harvesting XRechnung + parallelbetrieb (2026-05-21, same session) + +User strategic reframe: **MedCare-rs and smb-office-rs are transcodes that never worked as Rust** — they're pre-prod Rust ports of C# WinForms desktop apps whose UIs can't be moved to web cheaply. They compile, they pass tests, but they don't "look like anything" until the WinForms layer is replaced (a separate large effort outside this plan's scope). **woa-rs is different — it's a web application transcode** (Python/Flask → Rust/axum), which means the same kind of work that produces binaries-only feedback in MedCare/SMB produces VISIBLE WEB-UI feedback in woa. That asymmetry is what makes woa-rs the rewarding integration target. + +§9 (hot/cold split) + §10 (OGIT-in-sea-orm) already revised the technical effort estimate downward. §11 reframes the STRATEGIC sequencing — woa-rs is where the integration story should land first, because every PR shows up in a browser, not just in a test report. + +### 11.1 Three harvests, one rewarding target + +``` + woa-rs + ┌────────── (web-app target) ──────────┐ + │ │ + │ already in tree: │ + │ • OGIT TTL at vendor/ogit/v02-harvest │ + │ • sea-orm entities mirrored from OGIT │ + │ • MySQL via DualSink-Pivot writer- │ + │ parity │ + │ • axum + askama + tower-sessions │ + │ web stack │ + │ │ + └────────────────┬────────────────────────┘ + │ + ┌──────────────────┼────────────────────────┐ + ▼ ▼ ▼ + ┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────────┐ + │ HARVEST 1 (SMB): │ │ HARVEST 2 (Med): │ │ HARVEST 3 (in-tree):│ + │ XRechnung pattern │ │ parallelbetrieb │ │ OGIT wiring │ + │ │ │ reconciler │ │ │ + │ source files: │ │ source files: │ │ source files: │ + │ • hydrate_zugferd │ │ • MedcareMysql- │ │ • vendor/ogit │ + │ • hydrate_zugferd_ │ │ Reconciler │ │ • sea-orm entities │ + │ rules │ │ (mysql_recon- │ │ • lance-graph- │ + │ • SchematronHydrator│ │ ciler.rs, 461 │ │ ontology hydrators│ + │ • XsdHydrator │ │ LOC, 11 tests) │ │ (hydrate_dolce + │ + │ • collect_xsd_files │ │ • parallelbe- │ │ provo + qudt + │ + │ • EN16931 rule set │ │ trieb trait │ │ schemaorg + │ + │ │ │ • C# parity │ │ fibo_fnd + skr03/ │ + │ delivers to woa-rs: │ │ probe pattern │ │ skr04) │ + │ X-Rechnung output │ │ │ │ │ + │ for Stefan's │ │ delivers: │ │ delivers: │ + │ invoices (visible │ │ MySQL ↔ Lance │ │ /api/__graph route │ + │ in browser as │ │ drift dashboard │ │ exposing Cypher │ + │ downloadable XML + │ │ at /api/__parity │ │ over the woa │ + │ rendered PDF) │ │ (visible admin │ │ ontology (visible │ + │ │ │ panel) │ │ in browser) │ + └─────────────────────┘ └──────────────────┘ └─────────────────────┘ +``` + +### 11.2 Why this is "quick and rewarding" + +The reward function is **time-to-visible-feedback per PR**. For each plan: + +| Consumer | Time-to-visible per PR | Why | +|---|---|---| +| **woa-rs** | **Minutes.** Every PR adds a route handler, an askama template, a database column, or a config that surfaces in the browser. PR-1 lands → smoke test against `localhost:8080/vorgaenge/123` shows the change. | It's a web app. Every change has a URL. | +| **smb-office-rs** | Days-to-weeks. PRs land in `smb-bridge`, `smb-ontology`, `smb-mongo`, but the C# WinForms UI on the customer's desktop is what makes the change visible — and that UI isn't ours to touch. FFI smoke tests confirm linkage, not user-visible behavior. | The desktop UI is outside the port. | +| **MedCare-rs** | Days-to-weeks. Same shape — `medcare-server` is technically axum, but the customer-facing experience is the C# WinForms MedCare app via the parity tool. Drift dashboards are admin-only and surface only when the C# probe runs. | Customer-facing surface is C#. | + +**This explains why §10's "MedCare is the quickest target for end-to-end proof" is true for ENGINEERING but woa-rs is the right target for DEMONSTRATION.** Engineering completeness ≠ user-visible reward. Stefan (the WoA end user, per `woa-rs/CLAUDE.md` glossary) sees the wins in his browser the day a PR lands. + +### 11.3 The harvest sequence — concrete PR ladder + +Suggested PR ladder for the "quick and rewarding" path. Each PR is one ascending integration milestone visible in the browser: + +| # | Source | Lands in woa-rs as | Visible in browser as | +|---|---|---|---| +| 1 | (greenfield) | Phase 0 vendor symlinks + workspace exclude | (build green; no UI change yet) | +| 2 | smb-office-rs `smb-bridge` shape + MedCare-rs `MedcareRegistry` | `crates/woa-bridge/` + `crates/woa-ontology/` skeleton crates + `WoaRegistry::hydrate(...)` helper (D-WLG-3/D-WLG-4 per §3) | (build green; cargo test passes; no UI yet) | +| 3 | smb-office-rs `unified_bridge_wiring.rs::smb_unified_bridge` | `woa_unified_bridge(registry, actor_role, tenant)` constructor (D-UB-4 per `unified-bridge-consumer-migration-v1.md`) | (`/api/v1/health` returns "ontology hydrated: WorkOrder" — first visible signal) | +| 4 | in-tree OGIT + lance-graph-ontology hydrators | Boot-time hydration menu: `hydrate_dolce + hydrate_provo + hydrate_qudt + hydrate_schemaorg + hydrate_fibo_fnd + hydrate_skr03 + hydrate_skr04` (D-WLG-8 lance-cache feature) | (`/api/__ontology` admin route lists hydrated G-slots + entity counts per family) | +| 5 | **HARVEST 1 (SMB):** `hydrate_zugferd` + `hydrate_zugferd_rules` + `SchematronHydrator` + `XsdHydrator` | woa-rs ZUGFeRD/Factur-X invoice generator: `POST /vorgaenge//rechnung/xrechnung` → returns conformant XML + downloadable Factur-X PDF | **Stefan clicks "X-Rechnung erstellen" on a workorder, downloads a valid EN16931-conformant invoice. Visible reward.** | +| 6 | **HARVEST 2 (MedCare):** `MedcareMysqlReconciler` shell + `parallelbetrieb::{Reconciler, DriftEvent, DriftField, DriftKind}` trait | `WoaMysqlReconciler` with sea-orm fetchers (D-WLG-15/16) + admin route `GET /api/__parity` (D-WLG-17 mirrors medcare-server's parity.rs ring buffer) | **Admin opens `/admin/parity` and sees green/red drift status per Customer row across MySQL ↔ Lance. First-class reconciler dashboard.** | +| 7 | smb-office-rs + MedCare-rs combined: RLS via `OntologyRegistry::enumerate("WorkOrder")` (D-WLG-10) | Per-tenant row filter on every route handler; `tenant_get_or_404` becomes the unified-bridge `authorize(owl, row_tenant, Read)` call | **Cross-tenant URL-guessing returns 404 (not 403); admin sees the per-tenant scope active in the parity panel.** | +| 8 | (opt-in) Phase 4 — lance-graph-planner Cypher endpoint | `POST /api/__graph` accepts Cypher / SPARQL / GQL | **`/admin/graph` becomes a query playground: "MATCH (c:Customer)-[:HAS_WORKORDER]->(wo:WorkOrder) WHERE wo.status = 'offen' RETURN c.firma, wo.betreff" returns results from the Lance projection.** | +| 9 | (opt-in) Phase 5 — CAM-PQ similarity | `EntityStore::similar_to` over Lance | **`/kunden//similar` returns the 10 most-similar customers by address + industry + recent-Vorgang-history. Sales-pipeline feature.** | + +Each rung produces a screenshot. Compare: an equivalent migration in MedCare-rs produces an audit-log entry in JSON, visible only to ops. + +### 11.4 What this changes about the harvest order across the three plans + +Previous framings: +- §10.6 (this plan) — "two-axis harvest from smb (bridge-wiring) + MedCare (reconciler pattern)" +- `lance-graph-in-smb-office-rs-v1.md` §7.4 — "smb ships UnifiedBridge template; MedCare + woa absorb it" +- `lance-graph-in-medcare-rs-v1.md` §9.4 — "MedCare is the right target for end-to-end proof" + +§11 refines: **all three are correct, but they answer different questions.** + +| Question | Right target | +|---|---| +| "Where does the canonical `_unified_bridge` constructor pattern live?" | smb-office-rs (`smb_unified_bridge` in `unified_bridge_wiring.rs`) | +| "Where does the canonical `MysqlReconciler` shell live?" | MedCare-rs (`MedcareMysqlReconciler` in `medcare-analytics/src/mysql_reconciler.rs`) | +| "Where does the canonical XRechnung / ZUGFeRD invoice flow live?" | smb-office-rs (consumes `hydrate_zugferd` + `hydrate_zugferd_rules` upstream) | +| "Where does an engineer see the unified-bridge stack working end-to-end with HIPAA + diverse-redundancy?" | MedCare-rs (highest substrate maturity; LanceProbe is the cross-language witness) | +| "Where does the customer / end user see the unified-bridge stack at all?" | **woa-rs** (the only web app; the only consumer where every PR has a URL) | + +The cross-consumer harvest is **not** linear (smb → MedCare → woa). It's **fan-in to woa**: SMB ships the bridge template + the XRechnung flow; MedCare ships the reconciler shell + the parity dashboard pattern; woa-rs is the customer-facing integration target that absorbs both + adds the visible web-UI surface. + +### 11.5 What stays unchanged + +- The §9 hot/cold split framing (lance-graph-ontology hot, sea-orm + MySQL cold) stays exactly as written. The harvest path INSIDE this framing. +- The §10 effort estimate revision (Phase 0-3 closure ~9-10 days with the OGIT-in-sea-orm shortcut) is the foundation §11's PR ladder runs on. §11 doesn't change the per-PR cost — it sequences the PRs by visible-reward. +- D-WLG-1..17 deliverable IDs unchanged. §11 just specifies the recommended SHIPPING ORDER. + +### 11.6 One-line summary + +> woa-rs is the rewarding integration target because it's a web app — every PR shows up in a browser, not just in a test report. The harvest path is fan-in: SMB ships the `_unified_bridge` template + the XRechnung/ZUGFeRD invoice flow; MedCare ships the parallelbetrieb reconciler shell + the parity-dashboard pattern; woa-rs consumes both + adds the visible web-UI surface that Stefan can click through. Recommended PR sequence: scaffolding (1-2) → ontology hydration (3-4) → **XRechnung visible reward (5)** → **parity dashboard visible reward (6)** → tenant RLS (7) → Cypher playground (8) → similarity (9). From 9cecf2fa91f09469c7cd3cca2019514f370a3fcb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:36:47 +0000 Subject: [PATCH 15/22] =?UTF-8?q?docs(plans/woa-rs):=20=C2=A712=20?= =?UTF-8?q?=E2=80=94=20three-way=20reference=20asymmetry,=20codegen-bucket?= =?UTF-8?q?=20vs=20per-handler=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User architecture clarification: smb-office-rs and MedCare-rs both scraped business logic from already-working C# WinForms apps. woa-rs is different — it transcoded from Python (Flask/SQLAlchemy), uses reusable SoC + DTO mapping, and has 660 routes already harvested from Python source via codegen into Jinja → askama templates (per RFC v02-006). That changes the unified-bridge integration approach. §11 framed each integration step as "handler-level" — that's right at the user-visible layer but incomplete at the implementation layer. The actual change for most of the 660 routes is per-BUCKET via codegen, not per-handler. §12 NEW — covers: - Three-way source/reference table: · smb-office-rs: ~13 MongoDB collections, hand-mirror from C# · MedCare-rs: 104 MySQL tables → 7 Healthcare OGIT entities, hand-port + parity-witness via LanceProbe · woa-rs: 660 routes across 13 buckets, codegen-emitted from Python source per RFC v02-006 - Per-bucket codegen integration map (all 13 buckets covered): csrf_form_post_engine_call (194) + ajax_json (105) + list_for_tenant (80) + form_get_post (55) + detail_for_tenant (43) + soft_delete (41) + sa_admin_view (34) + download_blob (31) + pdf_render (22) + template_get (22) + signed_link_action (15) + get_redirect_shortcut (11) + other (0). One codegen-template edit propagates to every route in the bucket. - PR-7 RLS unification reframed as THE BIG ONE: one codegen edit to list_for_tenant + detail_for_tenant + csrf_form_post_engine_call buckets replaces ~317 hand-written tenant_get_or_404 call sites with state.unified_bridge. authorize(...). One PR, ~317 routes upgraded. - SoC + DTO seam is the right MIRROR target: WoaMysqlReconciler diffs DTOs (CanonicalCustomerRow per RFC v02-006 layer table), not entity rows. Reconciler doesn't author entity shapes; it consumes them. - Effort estimate refined: Phase 0-3 closure narrows from §10's ~9-10 days to ~7-8 days because route-handler integration is codegen-template work (~6 hours for 3 buckets), not per- handler edits (~3 days originally). - §11 PR ladder retained; §12 refines the implementation shape per rung. PR-5 (XRechnung) lands in pdf_render bucket. PR-6 (parity dashboard) lands in sa_admin_view bucket. PR-7 (RLS unification) is the cross-bucket codegen edit. The codegen + SoC + DTO architecture is the force multiplier that makes woa-rs the rewarding integration target despite its scale (660 routes vs MedCare's ~30-50 codebook slots). --- .claude/plans/lance-graph-in-woa-rs-v1.md | 102 ++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/.claude/plans/lance-graph-in-woa-rs-v1.md b/.claude/plans/lance-graph-in-woa-rs-v1.md index 6be4da0c..bda8028b 100644 --- a/.claude/plans/lance-graph-in-woa-rs-v1.md +++ b/.claude/plans/lance-graph-in-woa-rs-v1.md @@ -459,3 +459,105 @@ The cross-consumer harvest is **not** linear (smb → MedCare → woa). It's **f ### 11.6 One-line summary > woa-rs is the rewarding integration target because it's a web app — every PR shows up in a browser, not just in a test report. The harvest path is fan-in: SMB ships the `_unified_bridge` template + the XRechnung/ZUGFeRD invoice flow; MedCare ships the parallelbetrieb reconciler shell + the parity-dashboard pattern; woa-rs consumes both + adds the visible web-UI surface that Stefan can click through. Recommended PR sequence: scaffolding (1-2) → ontology hydration (3-4) → **XRechnung visible reward (5)** → **parity dashboard visible reward (6)** → tenant RLS (7) → Cypher playground (8) → similarity (9). + +--- + +## 12 — Three-way reference asymmetry: C# scrape vs Python+SoC+DTO codegen (2026-05-21, same session) + +User architecture clarification: **smb-office-rs and MedCare-rs both scraped their business logic from already-working C# WinForms apps.** They have working C# references to compare against (parity-witness pattern fits naturally — LanceProbe in MedCareV2, the future SMB equivalent). **woa-rs is structurally different: it transcoded from Python (Flask/SQLAlchemy), uses reusable Separation-of-Concerns + DTO mapping, and has 660 routes already harvested from Python source via codegen into Jinja → askama templates** (per `woa-rs/rfcs/v02-006-route-codegen-and-ontology-unification.md`). + +This changes how the unified-bridge integration should land in woa-rs. + +### 12.1 The three sources, restated + +| Consumer | Source | Working reference today | Transcoding approach | Scale at the boundary | +|---|---|---|---|---| +| **smb-office-rs** | C# WinForms ERP at `AdaWorldAPI/SMB-Office/1x1-prg` | Yes — the C# app runs in customer deployments | Hand-mirror of `db_*.cs` BSON schemas + per-route logic; `mongo-schema-warden` + `transcode-auditor` agents gate parity | ~13 MongoDB collections, ~30-entity ACCEPTED list per `foundry-roadmap-unified-smb-medcare-v1.md` | +| **MedCare-rs** | C# WinForms clinic-mgmt at `AdaWorldAPI/MedCare` | Yes — the C# app runs in praxis deployments + `MedCareV2/LanceProbe` is the cross-language parity tool | Hand-port of `pf_*`/`combo_*`/`praxis_*`/`glob_*` MySQL tables (104 total per the `MedCare-rs/.MYSQL/Struktur.sql` reality check); auth path's broken 3DES legacy carried forward via `legacy-tripledes-fallback` feature flag | 7 Healthcare OGIT entities (Patient/Diagnosis/LabValue/Medication/Treatment/Visit/VitalSign); ~30-50 codebook slots | +| **woa-rs** | **Python Flask/SQLAlchemy** at `AdaWorldAPI/WoA` | Yes — Stefan's deployment at `stefan280879/WoA` (Python WoA on MySQL) | **Codegen + SoC + DTO architecture: 660 routes classified into 13 buckets (per RFC v02-006); per-family manifest.yaml + routes.yaml + askama templates harvested from Python source via the route-codegen pipeline** | ~660 routes across ~20 functional families (vorgaenge, kunden, einstellungen, mahnwesen, stundenzettel, einsatz, logbook, …) | + +The bucket scale + codegen + SoC pivot is the load-bearing structural difference. SMB has ~13 BSON collections to migrate; MedCare has ~7 Healthcare entities; **woa has ~660 routes already classified, manifest-driven, and codegen-emitting**. + +### 12.2 What this means for the unified-bridge integration + +The §11 PR ladder framed each integration step as a "handler-level" change ("Stefan clicks X-Rechnung erstellen on a workorder"). That's right at the user-visible layer; it's incomplete at the implementation layer. **The actual change for most of the 660 routes is per-BUCKET, via codegen, not per-handler.** + +Concretely, the RFC v02-006 bucket taxonomy: + +| Bucket | Routes | % | Unified-bridge integration shape | +|---|---|---:|---| +| `csrf_form_post_engine_call` | 194 | 29.7% | Codegen emits `state.unified_bridge.authorize(owl_id, tenant, op)?` as the first line of every generated handler | +| `ajax_json` | 105 | 16.1% | Same — single codegen edit propagates to all 105 handlers | +| `list_for_tenant` | 80 | 12.3% | Codegen emits the per-tenant predicate via the unified bridge's `g_lock` | +| `form_get_post` | 55 | 8.4% | Same shape | +| `detail_for_tenant` | 43 | 6.6% | Same shape | +| `soft_delete` | 41 | 6.3% | Codegen emits a `Write` op authorize check | +| `sa_admin_view` | 34 | 5.2% | Codegen emits the cross-tenant audit hook via the bridge's `AuditSink` | +| `download_blob` | 31 | 4.7% | Codegen emits the `Read` op authorize check | +| `pdf_render` | 22 | 3.4% | Same shape | +| `template_get` | 22 | 3.4% | No tenant scope; codegen skips authorize when route has no entity scope | +| `signed_link_action` | 15 | 2.3% | Codegen emits timing-safe token compare + audit hook; bridge integration on the action handler | +| `get_redirect_shortcut` | 11 | 1.7% | Same as `template_get` | +| **`other`** | **0** | **0.0%** | 100% bucket coverage; no manual fallback needed | + +**A single per-bucket codegen edit propagates the unified-bridge integration to every route in that bucket.** 194 routes get authorize-checked from one codegen template change. Compare manual per-handler integration: 194 PR commits, 194 review surfaces, 194 places to drift. The codegen is the force multiplier. + +### 12.3 SoC + DTO means the bridge plugs in at the right seam + +Separation of Concerns in woa-rs (per the existing `crates/*` layout: `decimal_money`, `skr_data`, `buchungs_validator`, `woa_pdf`, future `crates/codegen`, future `crates/woa-bridge`) means each crate has a single declared responsibility. The unified bridge plugs in at exactly one seam: + +``` +Route handler (codegen-emitted, per-bucket) + │ + ▼ +state.unified_bridge.authorize(owl_id, tenant, op)? ←── ONE seam, integrated via codegen template change + │ + ▼ +Sea-orm Entity::find_by_id(...).await ←── cold-path read (DTO-mapped to render context) + │ + ▼ +Askama template render ←── visible to Stefan +``` + +The bridge is invisible inside the bucket-generic handler. The DTO mapping from sea-orm row → render context is unchanged. The Jinja-harvested askama template is unchanged. The codegen emits the bridge call as scaffolding the bucket template requires; per-handler customization stays in the per-handler override layer (per RFC v02-006 §"Architecture" `overrides//.rs.tmpl`). + +### 12.4 PR ladder refinement — codegen-bucket pivots vs per-handler edits + +§11's 9-rung PR ladder is mostly right but lumps codegen-bucket changes into "per-handler" framing. Refined: + +| § | §11 framing | §12 refinement | +|---|---|---| +| PR-3 | `woa_unified_bridge(...)` constructor + `/api/v1/health` smoke | Unchanged. Constructor lives in `crates/woa-bridge`, not bucket-level. | +| PR-4 | Boot-time hydration menu + `/api/__ontology` admin route | Unchanged. Boot-level wiring; not per-bucket. | +| PR-5 | **HARVEST 1 (SMB): XRechnung** | Refined: lands as ONE handler in the `pdf_render` bucket (per RFC v02-006 — `wo_to_invoice` shape). The codegen-bucket integration of `pdf_render` adds `state.unified_bridge` to the handler context; the XRechnung-specific logic is the per-handler override. Visible reward unchanged (Stefan downloads invoice). | +| PR-6 | **HARVEST 2 (MedCare): parity dashboard** | Refined: ADMIN-only route in the `sa_admin_view` bucket (cross-tenant; bridge `audit_required` is true). Codegen emits the audit hook + cross-tenant scope; per-handler override implements the WoaMysqlReconciler aggregation read. | +| PR-7 | Tenant RLS unification | **THIS IS THE BIG ONE** — refined: ONE codegen-template change to the `list_for_tenant` (80 routes) + `detail_for_tenant` (43 routes) + `csrf_form_post_engine_call` (194 routes; tenant-scoped) buckets replaces ~317 hand-written `tenant_get_or_404(...)` call sites with `state.unified_bridge.authorize(owl_id, tenant, op)?`. One PR, ~317 routes upgraded. Cross-tenant URL-guessing returns 404 across the whole app. | +| PR-8 | Cypher playground | Unchanged (new admin route, not a bucket integration) | +| PR-9 | Similarity | Unchanged (new admin/sales route, not a bucket integration) | + +PR-7 is the moment the unified-bridge integration looks **clean** — 317 routes upgraded by a single codegen-template change, all going through the same authorize call, all auditable from one log stream, all per-tenant scoped via the bridge's `g_lock`. + +### 12.5 The DTO/SoC layer is the right MIRROR target for the reconciler + +§10's framing — "MedCare-rs MysqlReconciler pattern transfers 1:1 with sea-orm fetchers because sea-orm is ergonomic" — is correct but understates the gain. The actual mirror target is **per-bucket DTO mapping**, not per-handler: + +- The `CanonicalCustomerRow` shape (§10.5 D-WLG-15) is the **DTO** for the Customer entity. The reconciler diffs DTOs, not Entity rows. +- Sea-orm `Entity::find_by_id` produces an `ActiveModel` that the bucket-generic handler then maps to a `CustomerDto` (per RFC v02-006 Layer table); the `CanonicalCustomerRow` is the deterministic projection of that DTO. +- A `WoaMysqlReconciler` Round-2 expansion (D-WLG-15 follow-ons for WorkOrder / Position / Mahnung / Tenant / User) is **one DTO definition + one fetcher impl per entity** — and the existing DTO definitions per RFC v02-006 layer table are mostly already authored. The reconciler doesn't author entity shapes; it consumes them. + +### 12.6 What this changes in §10.4 effort estimates + +§10.4 revised Phase 0-3 closure from ~13 days to ~9-10 days based on the OGIT-in-sea-orm shortcut alone. §12 narrows further: + +| Phase | §10 revised | §12 refined | +|---|---|---| +| Phase 0 | 1 day | 1 day (unchanged) | +| Phase 1 | 2 days | 2 days (unchanged) | +| Phase 2 (route-handler integration) | 3 days, "~6-8 handlers initially" | **1-2 days** — the route-handler integration is largely a codegen-template-edit, not per-handler. Cost is reading + writing one bucket-template per bucket touched. ~3 buckets × 2 hours each = ~6 hours of template work + per-bucket smoke test. | +| Phase 3 (lance-cache + Lance projection) | 3-4 days | **3 days** — D-WLG-9 (Lance projection) IS the WoaMysqlReconciler at the DTO layer; reuses the DTO shapes that already exist. | + +**Revised Phase 0-3 closure: ~7-8 days** (down from ~13 in original §3, then ~9-10 in §10). + +### 12.7 One-line summary + +> woa-rs's integration cost is bounded by codegen-template edits, not per-handler edits. The 660 routes across 13 buckets means a single per-bucket codegen change propagates the unified-bridge authorize call to all routes in that bucket (e.g., one edit upgrades 317 tenant-scoped routes in one PR — the §11 PR-7 RLS-unification step). The DTO/SoC layer means the reconciler mirror from MedCare-rs lands at the DTO seam, not the entity seam, reusing the existing per-bucket DTO definitions. Net Phase 0-3 closure: ~7-8 days. From 081079fb460caba6d914377c391e5b83bed91f2e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:38:51 +0000 Subject: [PATCH 16/22] =?UTF-8?q?docs(plans/woa-rs):=20=C2=A713=20?= =?UTF-8?q?=E2=80=94=20woa-rs=20=E2=86=94=20lance-graph=20wiring=20IS=20th?= =?UTF-8?q?e=20POC=20for=206=20months=20of=20substrate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User strategic stakes: lance-graph was worked on Claude-Code- intensively for up to 6 months. In that window, the project produced no end-user visible result — only mock GUIs, dashboards, unit tests, and the substrate itself. Wiring the lance-graph business-logic surface into woa-rs is therefore not just one consumer-integration plan among three; it IS the POC for the entire 6-month substrate effort. §13 NEW — covers: - 6-month substrate inventory: 22 crates, VSA bundling + Jirak + CAM-PQ + 7 GraphBLAS semirings + palette codec + attention table lookup + 36 thinking styles + MUL + 16-strategy planner + NARS + AriGraph + CausalEdge64 v2 + 4 substrate iron rules + 10+ ontology hydrators + 5 tenant bridges + 12-worker sprints + 4-savant ensemble + ATT NLSpec activation + sea-orm DTOs + parallelbetrieb + MongoDB bridge + OGIT TTL. End-user visible from any of this so far: mock GUIs only. - Five POC criteria woa-rs uniquely meets: 1. Real customer (Stefan deploys WoA in prod) 2. Real business logic (660 routes, ~20 functional families) 3. Real visibility (every PR has a URL) 4. Real failure modes (drift, RLS fail-OPEN, hydrator non- idempotency surface at customer-load) 5. Real economic feedback (Stefan's time saved is the metric) - Why MedCare and SMB don't substitute: MedCare has a real customer but the Rust port doesn't replace anything user- visible (C# WinForms still in use; LanceProbe is ops-visible); SMB is pre-prod (no customer exposed). Both are substrate proofs for internal audiences; only woa-rs is a substrate proof for a paying customer. - PR-5 elevated to "the moment lance-graph stops being substrate- only and starts being a customer-deliverable system." Smallest POC slice: one route handler + one customer-visible artefact (EN16931 XRechnung XML + Factur-X PDF). DATEV/X-Rechnung validation tools become the external success indicator. - P0/P1/P2 priority ranking across the three consumer plans: P0 = woa-rs Phase 0-3 + PR-5 (~7-8 days, POC milestone). P1 = woa-rs PR-6/7 + MedCare-rs Phase 1-3 + SMB-office-rs Phase A-B. P2 = post-POC opt-in features (Cypher, similarity, cross- consumer parity test). The lance-graph substrate ledger entries this PR-5 produces are the first "real-load test" entries for every primitive that participates: hydrator X, planner strategy Y, unified-bridge stage Z, RLS predicate W. If any regresses, PR-5 surfaces it the day it ships — that's the POC's diagnostic value. --- .claude/plans/lance-graph-in-woa-rs-v1.md | 86 +++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/.claude/plans/lance-graph-in-woa-rs-v1.md b/.claude/plans/lance-graph-in-woa-rs-v1.md index bda8028b..91a566e8 100644 --- a/.claude/plans/lance-graph-in-woa-rs-v1.md +++ b/.claude/plans/lance-graph-in-woa-rs-v1.md @@ -561,3 +561,89 @@ PR-7 is the moment the unified-bridge integration looks **clean** — 317 routes ### 12.7 One-line summary > woa-rs's integration cost is bounded by codegen-template edits, not per-handler edits. The 660 routes across 13 buckets means a single per-bucket codegen change propagates the unified-bridge authorize call to all routes in that bucket (e.g., one edit upgrades 317 tenant-scoped routes in one PR — the §11 PR-7 RLS-unification step). The DTO/SoC layer means the reconciler mirror from MedCare-rs lands at the DTO seam, not the entity seam, reusing the existing per-bucket DTO definitions. Net Phase 0-3 closure: ~7-8 days. + +--- + +## 13 — woa-rs ↔ lance-graph wiring IS the POC for 6 months of lance-graph substrate (2026-05-21, same session) + +User strategic stakes clarification: **lance-graph was worked on Claude-Code-intensively for up to 6 months. In that entire window, the project produced no end-user visible result — only mock GUIs, dashboards, unit-test feedback, and the substrate itself. Wiring the lance-graph business-logic surface into woa-rs is therefore not just one consumer-integration plan among three; it IS the POC for the entire 6-month substrate effort.** + +§11 (woa = rewarding target) + §12 (codegen-bucket integration) gave the engineering and shipping framing. §13 names the strategic stakes: **everything lance-graph has built between ~late 2025 and 2026-05-21 — every iron rule, every CAM-bar-code, every hydrator, every Cypher planner strategy, every UnifiedBridge primitive — gets its first end-user visible existence proof through this wiring.** The wiring is the deliverable; the substrate is what the deliverable proves. + +### 13.1 What "no visible results except mock GUI" means concretely + +Six months of work landed: + +- **Substrate** (22 crates, 7 in workspace, 15 excluded/standalone) — `lance-graph-contract`, `lance-graph-planner`, `lance-graph-ontology`, `lance-graph-callcenter`, `lance-graph-rbac`, `causal-edge`, `bgz17`, `bgz-tensor`, `deepnsm`, `holograph`, `jc`, `sigker`, ndarray, … +- **Algebra** — VSA bundling in d=10000 (Chapman-Kolmogorov semigroup), Jirak-2016 noise-floor calibration, CAM-PQ codec, 7 GraphBLAS semirings, palette-codec compression cascade, attention-as-table-lookup +- **Cognitive stack** — 36 thinking styles, MUL (Meta-Uncertainty Layer) Dunning-Kruger + trust qualia + compass + homeostasis + gate, 16-strategy unified planner, NARS inference, AriGraph triplet store, episodic memory ±5..±500, CausalEdge64 v2 layout +- **Substrate iron rules** — I-SUBSTRATE-MARKOV, I-NOISE-FLOOR-JIRAK, I-VSA-IDENTITIES, I-LEGACY-API-FEATURE-GATED +- **Ontology spine** — `lance-graph-ontology` with 10+ hydrators (DOLCE, OWL-Time, PROV-O, QUDT, schema.org, SKOS, FIBO-FND, FIBO-BE, Schematron, XSD, SKR DATEV, ZUGFeRD), `OntologyRegistry`, `NamespaceBridge`, the 5 default tenant bridges (Woa, Medcare, Ogit, Spear, SharePoint) +- **Sprints + governance** — 12-worker autoattended sprints, 4-savant ensemble (PP-13/14/15/16), CCA2A A2A pattern, ATT NLSpec activation, the entire `.claude/board/` ledger +- **Adjacent infrastructure** — sea-orm DTOs in woa-rs, parallelbetrieb reconciler in MedCare-rs, MongoDB bridge in smb-office-rs, the OGIT TTL upstream repo + +**End-user visible from any of this so far: mock GUIs only.** Dashboards exist (`/admin/parity` in MedCare-rs, the LanceProbe ParityPanel in MedCareV2), but only ops sees them. The cognitive stack runs in unit tests, not in any user-facing flow. The 16-strategy planner has no production callers. The CAM-PQ codec compresses things in benchmarks. The hydrators populate registries that nothing visible consumes. + +This is **expected for substrate work** — the value proposition is "build the spine first, then everything else gets cheap" — but it means there is **zero end-user adoption evidence** for any of it. Every architectural decision since ~late 2025 has been bet-the-substrate without a customer-visible payoff demonstrating return. + +### 13.2 Why woa-rs is the POC + +The criteria for "this proves the substrate worked": + +1. **Real customer.** Stefan deploys WoA in production (per `woa-rs/CLAUDE.md` glossary). The Rust port replacing Stefan's Python WoA is the first end-user contact between the lance-graph substrate and a real person paying for the result. +2. **Real business logic.** 660 routes, ~20 functional families (Vorgaenge, Kunden, Einsatz, Mahnwesen, Stundenzettel, Logbook, …) — not a synthetic benchmark, not a mock domain. The cognitive stack has to demonstrate it can handle invoice generation, dunning escalation, time tracking, German tax compliance. +3. **Real visibility.** Every PR has a URL. Stefan clicks through and sees the change. The substrate either delivers — visible PDFs, visible drift dashboards, visible cohort searches — or it doesn't. +4. **Real failure modes.** Hot/cold drift, RLS-fail-OPEN, hydrator-replay non-idempotency, codebook overflow, OWL reasoner timeouts — these stop being theoretical when there's a real customer with real data hitting real routes. +5. **Real economic feedback.** Stefan's time saved is the metric. Did the unified bridge make adding a new tenant cheaper? Did the parallelbetrieb reconciler catch a drift bug before it shipped? Did Cypher cross-entity queries replace a hand-rolled join that took 3 days to write? The answers are observable, not asserted. + +The other two consumer plans don't meet these criteria yet: + +- **MedCare-rs** has a real C# customer (the praxis users running MedCare today) but the Rust port doesn't replace anything user-visible yet — it sits behind LanceProbe as a parity witness; the user keeps using C#. The substrate proof MedCare-rs delivers is "drift events emit cleanly", which is ops-visible but not customer-visible. +- **smb-office-rs** is pre-prod (per §7.1 in the smb plan). No customer is exposed to it. + +woa-rs is the only consumer plan where landing the integration means **a customer sees lance-graph working through a route they care about**. Every other consumer is a substrate proof for an internal audience. + +### 13.3 Implication: PR-5 (XRechnung) is the POC milestone + +§11.3's PR ladder has PR-5 (HARVEST 1: SMB → XRechnung in woa-rs) as the first visible-reward step. §13 elevates: **PR-5 is the moment lance-graph stops being substrate-only and starts being a customer-deliverable system.** + +What lands at PR-5: + +- The OGIT spine is hydrated (PR-3/4). +- The unified bridge is constructed (PR-3). +- The CAM bar-code substrate (§4.5) is addressing real Customer rows. +- The hydrators (`hydrate_zugferd` + `hydrate_zugferd_rules` + `SchematronHydrator` + `XsdHydrator`) are producing valid EN16931 invoices for a real workorder. +- Stefan can issue an invoice that's electronically conformant for German Mehrwertsteuer reporting. **That's a service the C# WoA doesn't offer today.** + +PR-5 is the smallest possible POC slice: one route handler, one customer-visible artefact (the XRechnung XML + Factur-X PDF), demonstrating that the substrate composes end-to-end through the unified bridge into a customer-paid-for outcome. + +### 13.4 What success looks like + +For the next session(s) wiring woa-rs, success is observable in three places: + +| Surface | What "POC working" looks like | +|---|---| +| **Stefan's browser** | Clicking through `/vorgaenge//rechnung/xrechnung` returns a downloadable EN16931 invoice. Clicking `/admin/parity` shows green/red drift status per Customer row. Cross-tenant URL-guessing returns 404 (not 403). | +| **Stefan's accountant** | The XRechnung file imports cleanly into DATEV / X-Rechnung validation tools. The Factur-X PDF renders correctly in any conformant viewer. | +| **lance-graph's substrate ledger** | The `.claude/board/AGENT_LOG.md` for this PR sequence carries entries saying "first customer-visible exercise of [hydrator X, planner strategy Y, unified-bridge stage Z, RLS predicate W]." Every substrate primitive that participated in PR-5 gets its first real-load test. | + +If any of those don't materialize: the substrate has a regression, and PR-5 surfaces it the day it ships. That's the POC's diagnostic value — not "the substrate is good" but "we can tell when the substrate isn't good, because a real customer notices." + +### 13.5 What this changes about plan priorities across the three consumers + +The PR-ladder + visible-reward + substrate-proof reasoning across §11-§13 collapses to: + +| Priority | Plan / phase | Reasoning | +|---|---|---| +| **P0** | `lance-graph-in-woa-rs-v1.md` Phase 0-3 (~7-8 days per §12.6) + §11.3 PR-5 (XRechnung visible reward) | First end-user-visible existence proof of the 6-month lance-graph substrate. POC milestone. | +| **P1** | `lance-graph-in-woa-rs-v1.md` PR-6/7 (parity dashboard + tenant RLS unification, ~3-4 days) | Second + third visible-reward milestones; both close major substrate gaps. | +| **P1** | `lance-graph-in-medcare-rs-v1.md` Phase 1-3 (D-LGMC-1 build fix + D-LGMC-4 unified-bridge constructor + D-LGMC-2/3 RLS, ~5-7 days per §9.3) | Engineering-completeness POC; complements PR-5 by showing the substrate also works for a HIPAA-regulated transcode (not just a web app). | +| **P1** | `lance-graph-in-smb-office-rs-v1.md` Phase A-B (D-LGSMB-1/2/3 SmbBridge upstream + role groups, ~7 days per §2) | Template-source completion; ships the canonical `SmbBridge` type-parameter swap so the unified-bridge surface stabilizes across all consumers. | +| **P2** | `lance-graph-in-woa-rs-v1.md` Phase 4-5 (Cypher / SPARQL / CAM-PQ opt-in) + `lance-graph-in-medcare-rs-v1.md` Phase 6-7 + `lance-graph-in-smb-office-rs-v1.md` Phase D-E | Post-POC; once PR-5 establishes the integration works, the higher-leverage substrate surfaces (Cypher playground, similarity search) land as additive features. | +| **P2** | `unified-bridge-consumer-migration-v1.md` Tier D D-UB-11 (cross-consumer parity test) | Regression gate for the three-way harvest; lands after all three consumers' Phase 1-3 closure. | + +The P0 above is **one block of work** — the first POC slice — that ends at PR-5. Everything after is post-POC iteration on a proven integration. + +### 13.6 One-line summary + +> Wiring the lance-graph business-logic surface into woa-rs IS the POC for 6 months of substrate work that has shipped zero customer-visible outcomes. Every iron rule, every CAM-bar-code, every hydrator, every Cypher strategy, every UnifiedBridge primitive gets its first end-user existence proof at woa-rs PR-5 (XRechnung visible reward). The other two consumer plans (MedCare-rs end-to-end target; smb-office-rs template source) complement the POC but don't substitute for it — neither has a customer in the loop yet. PR-5 is the smallest possible POC slice: one route handler, one customer-visible artefact, demonstrating the substrate composes end-to-end into a Stefan-paid-for outcome. From 3e1139193eaddfa257d9800530f222f7b7c47e3f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:44:06 +0000 Subject: [PATCH 17/22] =?UTF-8?q?docs(plans):=20add=20POC=20roadmap=20?= =?UTF-8?q?=E2=80=94=20woa-rs=20PR-5=20(XRechnung)=20as=20first=20customer?= =?UTF-8?q?-visible=20artefact?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User directive: refer to open plans + status in lance-graph board / plans dirs, add priorities, make an integration plan using the provided framing. Plus three attached distillation docs (DOLCE hydrator spec, Odoo work-steal, ERP Foundry HHTL L1-L4 distillation) folded in. NEW lance-graph-business-logic-poc-via-woa-rs-v1.md (236 LOC): - §1 Why: 6 months of substrate work, zero customer-visible outcomes; this plan locks the first POC slice. - §2 POC milestone: woa-rs PR-5 (XRechnung visible reward). Maps 1:1 to "First Foundry-style projection: fibo:Transaction" from the third attached distillation doc Phase 9. - §3 Priority ranking with concrete effort estimates: · P0: woa-rs Phase 0-3 + PR-5 (~7-8 days, POC milestone) · P1: woa-rs PR-6/7 + MedCare Phase 1-3 + SMB Phase A-B (~15-20 days net) · P2: opt-in Cypher / similarity / cross-consumer parity test · P3: Odoo work-steal extraction (parallel stream) - §4 Integrated 9-rung PR ladder where each rung produces a screenshot or external-validation result. - §5 L1-L4 layered ontology dependency map per the third distillation doc. Status post-PR-407+408: L1+L2+L3+L4 partial; gaps in L3 (UBL, ISO 20022, XBRL GL→OWL mappers) and L4 (HGB/UStG/GoBD/Datev DTVF) are P2/P3 fill-in AFTER POC ships. - §6 Cross-plan D-id index by priority (no new D-ids; this plan re-indexes existing IDs). - §7 Parallel substrate dependencies (active plans that interact with the POC path); single non-trivial coordination is the unpushed D-SDR-3/4/5 follow-on PR. - §8 Risk register (9 risks; mitigations named per risk). - §9 Success criteria: four observable surfaces green simultaneously for ≥1 calendar day after PR-5 ships. - §10-§12 Open questions + status + references (source plans + attached distillation docs + external validation targets: xeinkauf.de XRechnung validator + Mustang reference). INTEGRATION_PLANS.md appended with the new plan entry (tee -a; the file is in the deny list for Edit per the ATT activation). The three user-attached distillation docs are now referenced by file ID for future session-load (not committed to the repo; they're in /root/.claude/uploads/). --- .claude/board/INTEGRATION_PLANS.md | 11 + ...-graph-business-logic-poc-via-woa-rs-v1.md | 236 ++++++++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 .claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index 6207ecf1..b1497d55 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -668,3 +668,14 @@ Phase A ships dedicated `SmbBridge` upstream (~50 LOC + 2 tests). Phase B author ### Scope Phase 1 fix the lance-phase2 build (`MedcareOntology::default()` calls broken no-arg form). Phase 2 close RLS fail-OPEN for Treatment / Visit / VitalSign. Phase 3 ship `medcare_unified_bridge` constructor. Phase 4 wire `MulThresholdProfile::MEDICAL` + `ontology_context_id` third axis (§73 SGB V Überweisung). Phase 5 unblock LanceProbe M2..M6 with the 5 medcare-rs endpoints (D-LGMC-7..11). Phases 6-7 opt-in Cypher / CAM-PQ. Two-branch reality: `main` (full lance-phase2) vs `claude/scaffold-medcare-rs-rZD5A` (lean fallback) — most deliverables land on `main` only. + +## 2026-05-21 — lance-graph-business-logic-poc-via-woa-rs-v1 (consolidating POC roadmap across the 4 consumer plans) + +**Status:** Active (Draft) +**Confidence:** HIGH on the POC framing (per 4 predecessor plans' session-appended §§4.5-4.7 + §§8-13 refinements + 3 attached distillation docs); MED on RFC v02-006 codegen-pipeline readiness (DRAFT — emitter side may need build). +**Plan file:** `.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md` +**Predecessors:** unified-bridge-consumer-migration-v1; lance-graph-in-{woa-rs,smb-office-rs,medcare-rs}-v1; super-domain-rbac-tenancy-v1. + +### Scope + +Consolidates the 4 consumer-integration plans into a P0/P1/P2-prioritised POC roadmap. **First POC slice = woa-rs PR-5 (XRechnung visible reward)** — the moment 6 months of lance-graph substrate work produces its first customer-deliverable artefact (EN16931-conformant ZUGFeRD/Factur-X invoice via `hydrate_zugferd` + `SchematronHydrator` + `XsdHydrator`). Maps 1:1 to "First Foundry-style projection: fibo:Transaction" Phase 9 from `erp_foundry_hhtl_ontology_distillation.md`. P0 effort ~7-8 days. P1 closes parity dashboard + RLS-via-codegen-bucket + MedCare/SMB cross-consumer harvest. P2 = opt-in Cypher/similarity/MongoDB alt cold path. No new D-ids; this plan re-indexes existing D-UB-/D-WLG-/D-LGMC-/D-LGSMB- IDs by priority. diff --git a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md new file mode 100644 index 00000000..c9318261 --- /dev/null +++ b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md @@ -0,0 +1,236 @@ +# Lance-Graph Business-Logic POC via woa-rs — v1 + +> **Author:** main thread (Opus 4.7), session 2026-05-21 (branch `claude/activate-lance-graph-att-k2pHI`) +> **Status:** Active (Draft) +> **Scope:** Consolidating integration plan that sequences the three consumer-plan harvests (`lance-graph-in-{woa-rs,smb-office-rs,medcare-rs}-v1`) + the `unified-bridge-consumer-migration-v1` substrate work into a single P0/P1/P2-prioritised POC roadmap, with **woa-rs as the customer-visible target** and PR-5 (XRechnung) as the POC milestone. This plan does not introduce new D-ids — it references existing ones from the four predecessor plans, locks in priorities, and names the cross-plan dependencies. +> **Path:** `.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md` +> **Confidence:** HIGH on the POC framing (per the four predecessor plans' session-appended refinements §8-§13 across the three consumer plans + §4 of the unified-bridge plan); MED on parallel-substrate dependencies (the convergence-v1 / RBAC / callcenter-membrane work timing is outside this plan's control); HIGH on the codegen-bucket force multiplier (per RFC v02-006 13-bucket coverage of all 660 woa routes). +> **Predecessors:** +> - `unified-bridge-consumer-migration-v1.md` (§4.5 CAM bar codes, §4.6 read-only spine, §4.7 per-OGIT storage) +> - `lance-graph-in-woa-rs-v1.md` (§9 hot/cold, §10 OGIT-in-sea-orm, §11 rewarding target, §12 codegen-bucket integration, §13 POC framing) +> - `lance-graph-in-medcare-rs-v1.md` (§8 parallelbetrieb shipped + MongoDB alt cold path, §9 quickest substrate-complete target) +> - `lance-graph-in-smb-office-rs-v1.md` (§7 empty + canonical UnifiedBridge template source) +> - `super-domain-rbac-tenancy-v1.md` (Tier A D-SDR-1..5; the RBAC substrate the consumer plans bind to) + +--- + +## 1 — Why this exists + +Six months of lance-graph substrate work — 22 crates, VSA bundling, CAM-PQ codec, 36 thinking styles, 16-strategy planner, NARS inference, AriGraph triplet store, 10+ ontology hydrators, 5 tenant bridges, 4 substrate iron rules, ATT NLSpec activation, multi-sprint A2A pattern, parallelbetrieb reconciler, MongoDB bridge, sea-orm DTOs, OGIT TTL — has shipped **zero end-user visible outcomes**. Every architectural decision has been bet-the-substrate without customer-visible payoff demonstrating return (per `lance-graph-in-woa-rs-v1.md` §13.1). + +Three consumer integration plans landed earlier in this session, each with a different role: SMB ships the canonical UnifiedBridge wiring template + XRechnung pattern (`lance-graph-in-smb-office-rs-v1.md` §7); MedCare ships the parallelbetrieb reconciler shell + parity-dashboard pattern (`lance-graph-in-medcare-rs-v1.md` §8); woa-rs absorbs both and adds the customer-visible web-UI surface (`lance-graph-in-woa-rs-v1.md` §11). The three are complementary; **only woa-rs has a paying customer in the loop** (Stefan, per the WoA glossary). + +This plan locks the sequencing across the three consumer plans into one ranked roadmap where the **first POC slice (woa-rs PR-5, XRechnung visible reward) is the moment lance-graph stops being substrate-only and starts being a customer-deliverable system**. Everything after PR-5 is post-POC iteration on a proven integration. + + +## 2 — The POC milestone + +**The first POC slice is `woa-rs` PR-5 (XRechnung visible reward).** It is the moment lance-graph stops being substrate-only and starts being a customer-deliverable system. PR-5 ships: + +- One route handler in the `pdf_render` bucket (per RFC v02-006): `POST /vorgaenge//rechnung/xrechnung`. +- One customer-visible artefact: an EN16931-conformant ZUGFeRD/Factur-X invoice (XML + embedded-PDF/A-3). +- One end-to-end existence proof of: + - The OGIT spine (hydrated at boot per `unified-bridge-consumer-migration-v1.md` §4.3). + - The CAM bar-code substrate (`OwlIdentity` addressing a real Customer row per §4.5). + - The hydrators (`hydrate_zugferd` + `hydrate_zugferd_rules` + `SchematronHydrator` + `XsdHydrator`) producing a valid invoice from a real workorder. + - The unified bridge's 4-stage authorize over the customer's tenant + role. + - The sea-orm cold-path read + Lance hot-path projection (per `lance-graph-in-woa-rs-v1.md` §9). + +This maps 1:1 to **Phase 9 of `erp_foundry_hhtl_ontology_distillation.md`** ("First Foundry-style projection: `fibo:Transaction` Object Type with end-to-end ingest path") — viewed from the consumer-side instead of the substrate-side. The two framings converge: the substrate plan calls it "first Foundry-style projection"; this POC plan calls it "first customer-visible artefact." + +External validation surfaces — what makes the POC observably "working": + +| Surface | What "POC working" looks like | +|---|---| +| Stefan's browser | `/vorgaenge//rechnung/xrechnung` returns a downloadable Factur-X PDF + raw XML side-channel. | +| Stefan's accountant | The XRechnung file imports cleanly into DATEV / X-Rechnung official validation tools (`https://xeinkauf.de/xrechnung/`). The Factur-X PDF renders correctly in any conformant viewer. | +| lance-graph's substrate ledger | `.claude/board/AGENT_LOG.md` carries entries naming PR-5's "first real-load test" for each substrate primitive exercised: hydrators (`hydrate_zugferd`, `hydrate_dolce`, `hydrate_provo`, `hydrate_qudt`, `hydrate_skr03/04`), unified-bridge stages (chinese-wall + super-domain + role-group + slot redaction), planner strategies (TBD if PR-5 uses Cypher; otherwise direct sea-orm), Lance projection columns, CAM-PQ codec (TBD — XRechnung itself doesn't need similarity search, but reading-Customer-by-bar-code does), Schematron + XSD rule sets. | + +## 3 — Priority ranking + +Per `lance-graph-in-woa-rs-v1.md` §13.5, the cross-plan priorities are: + +| Priority | Plan / phase | LOC + days | Reasoning | +|---|---|---|---| +| **P0** | `lance-graph-in-woa-rs-v1.md` Phase 0-3 + PR-5 (XRechnung visible reward) | ~7-8 days net (per §12.6 refined estimate) | First end-user-visible existence proof of the 6-month lance-graph substrate. POC milestone. | +| **P1** | `lance-graph-in-woa-rs-v1.md` PR-6 (parity dashboard) + PR-7 (RLS unification via codegen-bucket pivot) | ~3-4 days | Second + third visible-reward milestones; PR-7 alone upgrades ~317 routes via one codegen edit (per §12.4). | +| **P1** | `lance-graph-in-medcare-rs-v1.md` Phase 1-3 (D-LGMC-1 build fix → D-LGMC-4 unified-bridge constructor → D-LGMC-2/3 RLS close) | ~5-7 days | Engineering-completeness POC; complements PR-5 by showing the substrate also works for a HIPAA-regulated transcode (not just a web app). | +| **P1** | `lance-graph-in-smb-office-rs-v1.md` Phase A-B (D-LGSMB-1/2 SmbBridge upstream + D-LGSMB-3 type-param swap + Phase B TTL authoring + role groups) | ~7 days | Template-source completion; ships canonical `SmbBridge` so the unified-bridge surface stabilizes across all consumers. Unblocks D-UB-5 sister deliverable. | +| **P2** | All three plans' Phase 4-5 (Cypher / SPARQL / CAM-PQ opt-in) | ~10-18 days total | Post-POC; once PR-5 establishes the integration works, higher-leverage substrate surfaces (Cypher playground, similarity search, attention-as-table-lookup) land as additive features. | +| **P2** | `unified-bridge-consumer-migration-v1.md` Tier D D-UB-11 (cross-consumer parity test) | ~120 LOC + 4 tests | Regression gate; lands after all three consumers' Phase 1-3 closure to prevent per-consumer drift of the dictionary layer. | +| **P3** | Odoo work-steal extraction (per `b9531cf3-odoo_work_steal_distillation.md`) | ~1 weekend per Priority-1 module | Parallel stream; supplements the existing in-tree hydrators with broader ERP coverage. Most-mature lance-graph-ontology hydrators already cover the L1-L4 layered ontology per the bO-* series. Odoo extraction is a force multiplier for the L4 jurisdictional overlay (especially `l10n_de_skr03` / `l10n_de_skr04` refresh against current DATEV-maintained data) and a path to a Python adapter for the "two-version bridge" pattern. | + + +## 4 — The integrated PR ladder (P0 + P1 sequenced) + +Drawing from `lance-graph-in-woa-rs-v1.md` §11.3 + §12.4 (codegen-bucket refinements) + the third attached distillation doc's Phase 9 framing. Each rung produces a screenshot or external validation, not just a passing test. + +| # | Source plan | Lands in woa-rs as | Visible in browser as | +|---|---|---|---| +| 1 | (greenfield) | Phase 0 vendor symlinks (`vendor/lance-graph`, `vendor/ndarray`) + workspace exclude block | (build green; `cargo metadata` passes; no UI change) | +| 2 | mirror MedCare `MedcareRegistry` shape | `crates/woa-bridge/` + `crates/woa-ontology/` skeleton crates + `WoaRegistry::hydrate(...)` helper (D-WLG-3, D-WLG-4) | (build green; cargo test passes) | +| 3 | mirror smb `smb_unified_bridge` | `woa_unified_bridge(registry, actor_role, tenant)` constructor (D-UB-4 ≡ D-WLG-equiv) | `/api/v1/health` reports "ontology hydrated: WorkOrder" | +| 4 | in-tree OGIT TTL + lance-graph-ontology hydrators (per L1-L4 distillation Phase 1-7) | Boot-time hydration menu: `hydrate_dolce` (L1) + `hydrate_owltime` + `hydrate_provo` (L2; GoBD audit trail) + `hydrate_qudt` (L2; monetary unit + Stundenzahl) + `hydrate_schemaorg` (L3; commercial-web) + `hydrate_fibo_fnd` + `hydrate_fibo_be` (L3; financial primitives) + `hydrate_skr03` + `hydrate_skr04` (L4; German chart of accounts) (D-WLG-8 `lance-cache` feature) | `/api/__ontology` admin route lists hydrated G-slots + per-family entity counts | +| 5 | **HARVEST 1 (smb-office-rs):** `hydrate_zugferd` + `hydrate_zugferd_rules` + `SchematronHydrator` + `XsdHydrator` | woa-rs ZUGFeRD/Factur-X invoice generator: `POST /vorgaenge//rechnung/xrechnung` → returns conformant XML + downloadable Factur-X PDF | **POC MILESTONE — Stefan clicks "X-Rechnung erstellen" on a workorder, downloads a valid EN16931-conformant invoice.** Stefan's accountant validates against DATEV / official `https://xeinkauf.de/xrechnung/` tools. | +| 6 | **HARVEST 2 (MedCare-rs):** `MedcareMysqlReconciler` shell + `lance_graph_callcenter::transcode::parallelbetrieb::{Reconciler, DriftEvent, DriftField, DriftKind}` trait | `WoaMysqlReconciler` mirroring 1:1 with sea-orm fetchers (D-WLG-15) + production wiring (D-WLG-16) + admin route `GET /api/__parity` ring-buffer endpoint (D-WLG-17, mirrors `medcare-server::routes::parity::ingest_csharp` + dashboard read) | Admin opens `/admin/parity`, sees green/red drift status per Customer row across MySQL ↔ Lance projection. | +| 7 | smb-office-rs + MedCare-rs combined (codegen pivot per RFC v02-006 + §12.4) | Per-bucket codegen template emits `state.unified_bridge.authorize(owl_id, tenant, op)?` as first line of every generated handler in `list_for_tenant` (80 routes) + `detail_for_tenant` (43 routes) + `csrf_form_post_engine_call` (194 routes) = **317 routes upgraded by one codegen edit** | Cross-tenant URL-guessing returns 404 (not 403) across the entire app. Admin sees the per-tenant scope active in the `/admin/parity` panel. | +| 8 | (opt-in, P2) `lance-graph-planner` | `POST /api/__graph` accepts Cypher / SPARQL / GQL via the planner's 16-strategy dispatcher | `/admin/graph` becomes a query playground: `MATCH (c:Customer)-[:HAS_WORKORDER]->(wo:WorkOrder) WHERE wo.status = 'offen' RETURN c.firma, wo.betreff` returns results from the Lance projection. | +| 9 | (opt-in, P2) CAM-PQ via `lance-graph-contract::cam::CamCodecContract` | `EntityStore::similar_to` over Lance + CAM-PQ codec | `/kunden//similar` returns top-10 similar Customers by address + industry + recent-Vorgang-history. Sales-pipeline feature. | + +Rung 1-7 = P0 + P1 (the POC + the immediate post-POC iteration). Rungs 8-9 = P2 (opt-in once the POC is stable). The 9-rung sequence completes the visible-reward arc — every rung produces a screenshot or a validation-tool result. + +## 5 — L1-L4 layered ontology dependency map + +Per `f6b68582-erp_foundry_hhtl_ontology_distillation.md` §1-4. Each hydrator depends on its parent G-slot's existence (via `inherits_from: Some(OGIT::_V1.0)`), so the boot-time hydration menu must respect the layering: + +``` +L1 — Upper ontology (foundational) + hydrate_dolce (inherits_from: None — root) + │ + ▼ +L2 — Cross-domain alignment (every L3+ depends on these) + hydrate_owltime ─┐ + hydrate_provo ─┼─ all inherits_from: Some(OGIT::DOLCE_V1.0) + hydrate_qudt ─┤ + hydrate_skos ─┘ + │ + ▼ +L3 — Industry business ontologies + hydrate_schemaorg (e-commerce / commercial-web) + hydrate_fibo_fnd (FIBO Foundations) + hydrate_fibo_be (inherits FIBO_FND) (FIBO Business Entities) + [TBD bO-* future]: + XBRL GL → OWL mapper (journal-entry interchange) + IFRS Taxonomy → OWL mapper (financial reporting) + UBL 2.4 → OWL mapper (canonical e-invoice format — parent of XRechnung + ZUGFeRD) + ISO 20022 → OWL mapper (financial messaging) + │ + ▼ +L4 — Jurisdictional overlay (German for woa-rs) + hydrate_skr03 + hydrate_skr04 (+ hydrate_skr03_bau for construction) (German chart of accounts) + hydrate_zugferd + hydrate_zugferd_rules (German B2G e-invoice — XRechnung/Factur-X) + SchematronHydrator + XsdHydrator (used by hydrate_zugferd_rules) + [TBD bO-* future]: + HGB / UStG term extraction (~180 German legal terms) + GoBD SHACL constraints (German digital-bookkeeping audit-trail rules) + Datev DTVF parser (Layer-5 operational format) +``` + +**Coverage status post-PR-407 + PR-408:** L1 + L2 + L3 (DOLCE/OWL-Time/PROV-O/QUDT/SKOS/schema.org/FIBO-FND/FIBO-BE) all shipped. L4 partial: SKR03/04 shipped, ZUGFeRD + ZUGFeRD rules shipped, Schematron + XSD hydrators shipped. Still gaps: UBL, ISO 20022, XBRL GL → OWL mappers (L3); HGB / UStG / GoBD / Datev DTVF (L4). The POC (PR-5 XRechnung) does NOT depend on the L3 XBRL/UBL/ISO 20022 mappers — `hydrate_zugferd` jumps directly from L4 to the EN16931 invoice schema via `XsdHydrator`. The unmapped L3 standards become P2 / P3 fill-in work after the POC ships. + +## 6 — Cross-plan deliverable index + +D-id origin lookup (no new IDs introduced by this plan; this is a re-indexing): + +| D-id | Origin plan | Description | Priority per §3 | +|---|---|---|---| +| D-UB-1 | unified-bridge | Consumer-pattern doc + signature shape | P1 | +| D-UB-2 | unified-bridge | `SmbBridge` skeleton upstream | P1 (blocks D-LGSMB-3) | +| D-UB-3 | unified-bridge | `lance_cache::ontology_cache_schema()` + `LanceCacheBootStrategy` | P1 | +| D-UB-4 | unified-bridge ≡ D-WLG-3 effect | woa-rs `woa_unified_bridge` constructor | **P0** (rung 3) | +| D-UB-5 | unified-bridge ≡ D-LGSMB-3 effect | smb-bridge type-param swap | P1 | +| D-UB-6 | unified-bridge ≡ D-LGMC-4 effect | medcare-bridge `medcare_unified_bridge` constructor | P1 | +| D-UB-7 | unified-bridge ≡ D-LGMC-1 effect | Fix `ontology_dto.rs:85` lance-phase2 build | P1 (blocks all medcare work) | +| D-UB-8 | unified-bridge ≡ D-LGMC-2 effect | medcare RLS for Treatment/Visit/VitalSign (fail-OPEN close) | P1 (safety-critical) | +| D-UB-9 | unified-bridge ≡ D-LGMC-5 effect | medcare `MulThresholdProfile::MEDICAL` | P1 | +| D-UB-10 | unified-bridge ≡ D-LGMC-6 effect | medcare `ontology_context_id` RLS axis | P1 | +| D-UB-11 | unified-bridge | Cross-consumer parity test | P2 | +| D-UB-12, 13, 14 | unified-bridge §4.6 | Read-only registry surface lock + proposal_sha256 idempotency test + versioned G-slot migration smoke | P1 | +| D-WLG-1..2 | woa-rs | Phase 0 vendor symlinks + workspace deps | **P0** (rungs 1) | +| D-WLG-3..4 | woa-rs | Phase 1 woa-bridge + woa-ontology crates | **P0** (rung 2) | +| D-WLG-5..7 | woa-rs | Phase 2 route-handler integration via codegen (Mandant↔TenantId mapping + actor_role mapping) | **P0** (rungs 3+7) | +| D-WLG-8..10 | woa-rs | Phase 3 lance-cache + Lance projection + RLS | **P0** (rung 4) + P1 (rung 7) | +| D-WLG-11..14 | woa-rs | Phase 4-5 Cypher + CAM-PQ | P2 (rungs 8-9) | +| D-WLG-15..17 | woa-rs §10.5 | WoaMysqlReconciler + production wiring + drift dashboard | P1 (rung 6) | +| D-LGMC-1..11 | medcare-rs | All Phase 1-5 (build fix + RLS + constructor + MUL + parity endpoints) | P1 | +| D-LGMC-15..21 | medcare-rs §8.4 | Round-2 reconciler expansion + production wiring + persistent sink | P1 | +| D-LGMC-22..26 | medcare-rs §8.5 | MongoDB alt cold path propagation from smb | P2 | +| D-LGSMB-1..6 | smb-office-rs | Phase A-C: SmbBridge upstream + role groups + tenant-type consolidation | P1 | +| D-LGSMB-7..9 | smb-office-rs | Phase D-E Cypher + CAM-PQ | P2 | + + +## 7 — Parallel substrate dependencies + +The POC P0 + P1 work does NOT block on (nor is blocked by) the workspace's active substrate plans, but coordination points matter: + +| Active substrate plan | POC interaction | +|---|---| +| `cognitive-substrate-convergence-v1` (sprint-12 Wave G, PR #390 + sprint-13 prep) | Substrate-only; provides CausalEdge64 v2 + QualiaI4 + WitnessCorpus. The POC consumes the SHIPPED parts (DOLCE/PROV-O/QUDT/SKR hydrators that landed via PR #383-389 + PR #407-408). No blocker. | +| `super-domain-rbac-tenancy-v1` (Tier A merged PR #363; D-SDR-3/4/5 unpushed) | The 4-stage `UnifiedBridge::authorize()` the POC consumes is in this plan. Tier A merged unblocks the POC; the 3 unpushed follow-on commits are needed for the full role-group + redaction-mask surface (PR-7 RLS unification). **Coordination point.** | +| `callcenter-membrane-v1` (DM-2/4/6 in PR; `claude/supabase-subscriber-wire-up`) | Provides the realtime Phoenix push + LanceVersionWatcher. POC doesn't strictly need it but PR-6 parity dashboard benefits from live update vs polling. P2 dependency. | +| `lance-graph-ontology-v5` (15 deliverables post-merge follow-ons) | Provides the post-PR-355 cleanup. The POC uses the already-shipped `OntologyRegistry` + `NamespaceBridge` + `MappingProposal`; the V5 follow-ons are mostly internal hygiene. No blocker. | +| `2026-05-06-splat-osint-ingestion-v1` (PR 1+2 in flight) | Orthogonal — splat ingestion is the OSINT super-domain story, not the WorkOrderBilling super-domain. No interaction with the POC. | +| `causaledge64-mailbox-rename-soa-v1` (specs SHIPPED PR #372; impl QUEUED) | Substrate-only; CausalEdge64 v2 layout reclaim is pre-existing dependency. POC consumes via the shipped `causal-edge` crate. No blocker. | + +The single non-trivial coordination is **D-SDR-3/4/5 (super-domain Tier A follow-on commits, currently unpushed)** — those ship the full `RoleGroup` + `FieldRedactionMask` + `SuperDomainEntry` table that PR-7 RLS unification depends on. If those don't land before PR-7, the codegen edit at PR-7 substitutes placeholder predicates that pass tests but don't enforce the full per-slot redaction mask. Acceptable for POC; needs follow-up. + +## 8 — Risk register + +| Risk | Probability | Impact | Mitigation | +|---|---|---|---| +| RFC v02-006 route-codegen pipeline isn't ship-quality | MED | HIGH (PR-7 RLS unification depends on bucket-template edits) | Phase 1 = build the codegen first if RFC v02-006 status is still DRAFT at session start. The classifier is 100%-coverage per the RFC; the emitter side is the gap. | +| Lance-cache feature ships untested at scale | LOW | MED | D-WLG-8 lands with idempotency + restart-recovery + concurrent-writer ordering tests per §3 Phase 3 spec. Acceptable risk for POC; production hardening is P2 work. | +| ZUGFeRD/Factur-X validation tools reject the generated invoice | LOW | HIGH (POC visible-reward fails) | PR-5 acceptance test: bytewise diff against a Mustang-generated reference invoice for the same workorder. The lance-graph-ontology `hydrate_zugferd_rules` Schematron checks are pre-flight; external validation is the ground truth. | +| `OntologyRegistry` mutation surface is reachable from consumer crates (violating §4.6 read-only spine) | MED | HIGH (cache invalidation cascades; spine drifts under ad-hoc edits per §4.6 failure mode) | D-UB-12 locks the read-only invariant before PR-5 ships. ~30 LOC + 2 tests asserting consumer crates can't call mutate methods. | +| Per-OGIT CAM storage (§4.7) violated by global codebook table | LOW | HIGH (per-family bar codes shift; consumer caches go stale) | §4.7 codified as the default for any new CAM-shaped artifact. Code review surfaces violations; D-UB-14 versioned G-slot migration smoke test catches cross-G-slot contamination. | +| Stefan's MySQL data exposes a sea-orm migration drift | MED | MED | DualSink-Pivot 2026-05-15 writer-parity discipline (Python + Rust both write MySQL; reconciler witnesses). PR-6 WoaMysqlReconciler is the regression sensor for this. | +| ndarray::simd CPU-feature mismatch on Stefan's deployment hardware | LOW | LOW | ndarray's `simd_caps()` runtime detect + scalar fallback handles any CPU. Per `lance-graph/CLAUDE.md` §19.7. | +| Build cache exhaustion on CI (GHA "no space left on device" we hit earlier this session) | MED | LOW (the failed build, not a feature blocker) | CI workflow adds `df-cleanup` step. Out-of-scope for the POC plan but ops should track. | +| codex P1 review finds a v1-accessor-under-v2-feature mistake per `I-LEGACY-API-FEATURE-GATED` | MED | LOW (resolvable before merge) | Pre-merge codex review is the canonical gate. PR-5..7 all expected to ship through it. | + +## 9 — Success criteria + +The POC is considered successful when **all four observable surfaces are green simultaneously** for at least one calendar day after PR-5 ships: + +1. **Stefan's browser** — `/vorgaenge//rechnung/xrechnung` returns 200 + a Factur-X PDF for at least 5 distinct workorders across at least 2 distinct tenants. +2. **External validation** — the generated XRechnung XML passes the official `https://xeinkauf.de/xrechnung/` validation tool with zero errors and zero warnings. +3. **`/admin/parity` dashboard** — PR-6 ships green status (Match) for ≥ 95% of sampled Customer rows over a 24-hour window; the < 5% drift gets investigated and the count drops monotonically. +4. **`.claude/board/AGENT_LOG.md` substrate ledger** — every substrate primitive exercised by PR-5 (per the §2 inventory) has a corresponding "first real-load test passed" entry written by the meta-agent after the smoke test concludes. + +Failure modes that DON'T count as POC failures (acceptable in a v1 POC; queued for P2): +- A specific Cypher query in the playground returns the wrong row count (Phase 4 opt-in, not POC scope). +- CAM-PQ similarity returns a customer-affinity ranking Stefan disputes (Phase 5 opt-in, not POC scope). +- One of the 660 routes outside the rung-7 codegen-bucket edit doesn't authorize correctly (pre-existing; PR-7's bucket edit is incremental). + +## 10 — Open questions + +1. **RFC v02-006 codegen pipeline readiness** — does the per-bucket emitter (`handler_kinds/*.rs`) exist as code today, or is it still RFC-stage? Phase 1 of this plan assumes the codegen is operable; if it's not, the first PR builds it. Probably worth a focused subagent read of `crates/codegen/` (if it exists) before scheduling PR-1. +2. **Stefan's deployment topology** — does Stefan run woa-rs on Railway (per `Cargo.toml` `repository = ...woa-rs`) or on his localhost? Affects the PR-5 visible-reward smoke test plan: if Railway, the demo is a URL Stefan opens; if localhost, the demo is a `cargo run -p woa-rs` we walk through with him. +3. **D-SDR-3/4/5 follow-on PR timing** — those commits exist locally on the lance-graph workspace per the substrate plan's status note but are unpushed. PR-7 RLS unification depends on them. Push when? +4. **Odoo work-steal scope for the POC window** — the third attached doc (Odoo distillation) lays out a parallel extraction stream. SKR03/04 are already shipped via `hydrate_skr03` / `hydrate_skr04` so the headline Phase O4 win is already realised. Is the Odoo `account.move` ↔ `fibo:Transaction` mapping (Phase O3) worth pulling forward to reinforce PR-5's `fibo:Transaction` projection? Probably P3 — defer. +5. **Foundry-style typed-object surface** — the third doc's §"Foundry-side semantic surface" maps Object/Link/Action/Function types to OWL/RDF substrate. PR-5 exercises Object Type (`fibo:Transaction`) + Link Types (the SPO triples wo→customer→country); PR-8 (Cypher playground) is closer to Function Type territory. Is exposing the typed-object surface as a first-class REST API in scope for the POC (e.g., `GET /api/object/transaction/` returning the typed-object JSON view), or is it post-POC iteration? Lean toward post-POC. +6. **HGB / UStG / GoBD term extraction** — the third doc names these as Phase 6-7 L4 work. The POC doesn't need them at PR-5 (XRechnung's compliance is via Schematron + EN16931, not HGB term resolution). Push to P2 / P3. + +## 11 — Status + +- **Architecture:** Working. Substrate + 4 consumer plans + 3 distillation docs all converge on the POC framing. +- **POC milestone (PR-5):** Not started. Critical path: Phase 0 → Phase 1 → Phase 2 (rung 3 `woa_unified_bridge`) → Phase 3 hydration menu (rung 4) → Phase 5 rung (XRechnung). +- **P0 estimated effort:** ~7-8 days net per `lance-graph-in-woa-rs-v1.md` §12.6. +- **P1 estimated effort:** ~15-20 days net across woa-rs PR-6/7 + medcare Phase 1-3 + smb Phase A-B. +- **P2 estimated effort:** ~10-18 days across all three plans' opt-in surfaces. + +**Confidence:** HIGH on the POC framing; HIGH on the substrate readiness (every primitive the POC depends on has shipped); MED on the codegen pipeline readiness (RFC v02-006 is DRAFT; emitter side is the gap); MED on coordination with the unpushed D-SDR-3/4/5 follow-on PR. + +## 12 — References + +### Source plans (in this workspace) +- `unified-bridge-consumer-migration-v1.md` — substrate spec + DTO mapper + CAM bar codes + read-only spine + per-OGIT storage +- `lance-graph-in-woa-rs-v1.md` — woa-rs consumer plan (with §9-§13 session refinements that gave this POC plan its framing) +- `lance-graph-in-smb-office-rs-v1.md` — smb-office-rs as template source +- `lance-graph-in-medcare-rs-v1.md` — MedCare-rs as substrate-complete target + MongoDB alt cold path +- `super-domain-rbac-tenancy-v1.md` — RBAC + tenancy substrate + +### Attached distillation docs (user-supplied 2026-05-21) +- `d7c12d03-prbO1dolcehydrator_1.md` — PR-bO-1 DOLCE+DUL hydrator spec (now SHIPPED via PR #407) +- `b9531cf3-odoo_work_steal_distillation.md` — Odoo as parallel extraction source for ERP semantics + l10n_de SKR03/04 chart refresh +- `f6b68582-erp_foundry_hhtl_ontology_distillation.md` — Master L1-L4 layered ontology distillation; this POC plan implements its Phase 9 ("First Foundry-style projection: `fibo:Transaction`") as the woa-rs PR-5 milestone + +### External validation targets +- `https://xeinkauf.de/xrechnung/` — official German XRechnung validator (POC external-validation surface) +- `https://www.ferd-net.de/zugferd/` — ZUGFeRD/Factur-X reference +- `https://docs.oasis-open.org/ubl/UBL-2.4.html` — UBL 2.4 (parent format for ZUGFeRD/XRechnung) +- Mustang library (`https://www.mustangproject.org/`) — reference implementation for bytewise validation of PR-5's generated invoices + +## 13 — One-line summary + +> The first POC slice is woa-rs PR-5 (XRechnung visible reward) — the moment 6 months of lance-graph substrate work produces its first customer-deliverable artefact. P0 effort: ~7-8 days. P1 closes immediate post-POC iteration (parity dashboard + RLS unification via codegen-bucket pivot + MedCare/SMB cross-consumer harvest). P2 lights up the opt-in Cypher / similarity / MongoDB-alt-cold-path surfaces. The L1-L4 layered ontology dependency map (per the third attached distillation doc) is post-PR-407 / 408 already covered through L4 for the German-jurisdictional path the POC exercises; gaps (UBL, ISO 20022, XBRL GL, HGB/UStG/GoBD term extraction, Odoo work-steal) are P2 / P3 fill-in work AFTER the POC ships. From e377d49bf0c25958b5047cbeb4652d5872d84e8a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 21 May 2026 17:57:03 +0000 Subject: [PATCH 18/22] =?UTF-8?q?docs(plans/poc):=20=C2=A714=20=E2=80=94?= =?UTF-8?q?=20Odoo=20OWL=20glue=20elevated=20to=20parallel=20substrate=20s?= =?UTF-8?q?tream=20feeding=20cognitive=20models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User strategic reframe: the Odoo-harvested OWL glue isn't a P3 nice-to-have — it's immediately important because the cognitive models need to be built AROUND it. In the end it's the most rewarding outcome once wired into lance-graph. §3 priority ranking originally put Odoo at P3 (parallel stream after POC). §14 elevates to P1 (parallel substrate stream running concurrent with the POC). §14 NEW — covers: - The structural claim: cognitive substrate is only as semantically grounded as the OWL it reasons over. L1-L4 hydrators (post-PR-407/408) give the formal ontology spine; they DON'T give the operational ERP vocabulary the cognitive models need (Partner, Account, Invoice, Stock, BOM, etc.). Odoo's 20+ years of ERP work IS that vocabulary, formalized as Python models + XML data files. - What "OWL glue" means concretely: alignment TTL that ties Odoo URIs to upstream OWL classes via owl:equivalentClass / owl:equivalentProperty (odoo:res.partner ↔ vcard:Kind, odoo:account.move ↔ gl-cor:entryHeader + fibo:Transaction, odoo:product.template ↔ schema:Product, etc.). The glue is what types semantic objects so the cognitive shader's 16-bit DOLCE slot classifier can run. - Why "build cognitive models around it" matters structurally: table of 6 cognitive layers showing what each does WITHOUT OWL glue (opaque strings, no semantic prior, undifferentiated row types, undefined DOLCE slots, edges without semantic identity) vs WITH OWL glue (FIBO-typed entities, dispatch by OWL type, per-super-domain MUL profile, pre-classified DOLCE slots at hydration time, semantically-identified edges). - The wiring path: Odoo source → crates/odoo-extract/ Rust AST walker → per-module TTLs → hand-curated alignment TTLs → hydrate_odoo_* functions → OntologyRegistry per-OGIT G-slots → cognitive substrate consumes OWL types natively at every decision point. - 10 new D-ODOO-* deliverables ranked by priority: · D-ODOO-1..5 + D-ODOO-7 = P1 (parallel with POC), ~10-12 days net total. D-ODOO-7 (Odoo→ZUGFeRD alignment) gated on PR-5 for cross-attribution. · D-ODOO-6 = P2 (SKR03/04 refresh; substrate already covered by in-tree hydrate_skr03/04 from DATEV CSV). · D-ODOO-8/9/10 = P2 (broader Odoo coverage + Python adapter + end-to-end demo). - §3 priority table updated: Odoo elevated from P3 to P1 parallel-concurrent-with-POC stream. - §14.7 names the rewarding end-state: once D-ODOO-10 lands, lance-graph reasons about ERP entities natively — Cypher queries cross-walk Odoo + DATEV SKR04 + FIBO in one query; NARS dispatches different evidence-update rules per OWL type; MUL picks per-super-domain threshold profile by OWL super-domain; cognitive shader's DOLCE slot pre-classifies every Odoo entity at hydration time per §4.4 O(1) inheritance; Foundry-style typed-object surface becomes navigable across Odoo's full operational vocabulary. - §14.8 what this DOES NOT change: PR-5 stays the POC milestone; L1-L4 layered map stays (Odoo lands on top via inherits_from); §4.6 read-only spine + §4.7 per-OGIT storage governance both apply to Odoo extraction (emits MappingProposal streams via append_proposals; lands at OGIT::ODOO__V1 per-module G-slots). The reward shape: PR-5 is the proof the wiring works on ONE route; D-ODOO-10 is the proof the wiring works on the entire ERP semantic surface. --- ...-graph-business-logic-poc-via-woa-rs-v1.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md index c9318261..674c6c04 100644 --- a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md +++ b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md @@ -234,3 +234,131 @@ Failure modes that DON'T count as POC failures (acceptable in a v1 POC; queued f ## 13 — One-line summary > The first POC slice is woa-rs PR-5 (XRechnung visible reward) — the moment 6 months of lance-graph substrate work produces its first customer-deliverable artefact. P0 effort: ~7-8 days. P1 closes immediate post-POC iteration (parity dashboard + RLS unification via codegen-bucket pivot + MedCare/SMB cross-consumer harvest). P2 lights up the opt-in Cypher / similarity / MongoDB-alt-cold-path surfaces. The L1-L4 layered ontology dependency map (per the third attached distillation doc) is post-PR-407 / 408 already covered through L4 for the German-jurisdictional path the POC exercises; gaps (UBL, ISO 20022, XBRL GL, HGB/UStG/GoBD term extraction, Odoo work-steal) are P2 / P3 fill-in work AFTER the POC ships. + +--- + +## 14 — Odoo OWL glue: parallel substrate stream feeding the cognitive models (2026-05-21, same session) + +User strategic reframe: **the Odoo-harvested OWL glue isn't a P3 nice-to-have — it's immediately important because the cognitive models need to be built AROUND it. In the end it's the most rewarding outcome once wired into lance-graph.** §3 priority ranking put Odoo at P3 (parallel stream); §14 elevates it to **parallel substrate stream feeding the cognitive models** — running concurrent with the POC, not after. + +### 14.1 The structural claim + +The cognitive substrate (thinking-engine, NARS inference, AriGraph triplet store, BindSpace SoA, CausalEdge64, MUL gate, 16-strategy planner) is only as semantically grounded as the OWL ontology it reasons over. Bar-code substrate without semantic anchor is just identity fingerprints addressing nothing meaningful. The L1-L4 layered hydrators shipped post-PR-407 + PR-408 give us the **formal ontology spine** (DOLCE upper + OWL-Time + PROV-O + QUDT + SKOS + FIBO-FND + FIBO-BE + schema.org + SKR03/04 + ZUGFeRD); what they DON'T give us is the **operational ERP vocabulary** that the cognitive models need to reason about Partners, Accounts, Invoices, Stock Moves, Manufacturing BOMs, HR Contracts, Payslips, Projects, Tasks, Mail Threads. + +Odoo has 20+ years of that operational vocabulary formalized as Python models + XML data files. Re-modelling it from FIBO/UBL/XBRL-GL alone is years of work; reading Odoo's source and emitting aligned OWL/SHACL is weeks (per `b9531cf3-odoo_work_steal_distillation.md` §"Premise"). **The Odoo work-steal is the cheapest path to a comprehensive OWL substrate for the cognitive models.** + +### 14.2 What "OWL glue" means concretely + +Per the third paragraph of the Odoo distillation doc and its §"Naming convention" + §"Concrete example" — the "glue" is the **alignment TTL** that ties Odoo URIs to upstream ontology classes: + +```turtle +@prefix odoo: . +@prefix fibo: . +@prefix vcard: . + +odoo:res.partner owl:equivalentClass vcard:Kind . +odoo:res.partner.vat owl:equivalentProperty fibo:hasTaxIdentifier . +odoo:res.partner.Company owl:equivalentClass fibo:LegalEntity . + +odoo:account.move owl:equivalentClass gl-cor:entryHeader, fibo:Transaction . +odoo:account.account owl:equivalentClass skr:Konto, fibo:Account . +odoo:account.tax owl:equivalentClass de:Steuerschlüssel . + +odoo:product.template owl:equivalentClass schema:Product . +odoo:uom.uom owl:equivalentClass qudt:Unit . +``` + +The glue is what makes a row of Odoo data reasonable-about as a `fibo:LegalEntity` + `vcard:Kind` simultaneously. The cognitive shader's 16-bit DOLCE slot (high-byte upper category + low-byte DnS role) classifies via the upper ontology; the OWL glue is what lets the slot classifier *know* that a `res.partner` row IS a `fibo:LegalEntity` (Endurant + Agent) and not, say, a `schema:Event` (Perdurant + Action). + +### 14.3 Why building cognitive models around it matters + +The cognitive shader doesn't reason about strings ("res.partner"); it reasons about typed semantic objects. **The OWL glue is what types them.** Without it: + +| Cognitive layer | What it does WITHOUT OWL glue | What it does WITH OWL glue | +|---|---|---| +| **AriGraph triplet store** | Stores triples like `` — opaque strings | Stores triples like `` — typed, reasoner-friendly | +| **NARS inference** | Operates on free-floating term identifiers; no semantic prior | Operates on FIBO-typed entities; can dispatch by `fibo:LegalEntity` vs `fibo:Counterparty` vs `vcard:Individual` | +| **MUL gate (Dunning-Kruger + trust)** | Calibrates uncertainty against undifferentiated "row types" | Calibrates differently per super-domain (Healthcare → conservative `MulThresholdProfile::MEDICAL`; WorkOrderBilling → permissive default) — the FIBO-shaped row IS the dispatch key | +| **16-strategy planner** | Selects strategy by query-shape heuristics | Selects strategy by query-over-FIBO-shape: a `MATCH (c:fibo:LegalEntity)` invokes different cost model than `MATCH (e:fibo:Transaction)`; the cognitive policy reads OWL types | +| **Cognitive shader 16-bit DOLCE slot classifier** | Returns slot 0xFFFF (undefined) for every row | Returns concrete (upper category, DnS role) for every row by walking `rdfs:subClassOf*` to the DOLCE root | +| **CausalEdge64 v2 (per `cognitive-substrate-convergence-v1.md`)** | Edges carry causal weights but no semantic identity | Edges carry causal weights AND a 16-bit DOLCE slot of source + target so SpoWitness chains can reason FIBO-typed | + +This is what "build cognitive models around it" means structurally. The cognitive models are *parameterised by* the OWL ontology; they read OWL types at decision points. The Odoo glue provides the operational vocabulary those decisions key against. + +### 14.4 The wiring path — Odoo extraction → lance-graph cognitive substrate + +The path from Odoo's Python source to lance-graph's cognitive runtime: + +``` +Odoo source tree Odoo extractor Per-module TTL Alignment TTL hydrate_odoo_* OntologyRegistry Cognitive substrate +(Python + XML + CSV) (Rust, crates/ (one per Odoo (hand-curated + per-module functions (CAM-addressable (thinking-engine, + odoo-extract/) module, e.g. LLM-draft-then- following the bar codes for NARS, MUL, planner, + (~1500 LOC) odoo/base.ttl, reviewed; ~100s of established bO-* every Odoo class + CausalEdge64, + odoo/account.ttl, axioms each) pattern (~50 LOC every alignment AriGraph, shader) + odoo/l10n_de_skr04.ttl) per hydrator) axiom) consume OWL types + natively at every + decision point + │ │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ ▼ + rustpython-parser AST walker per One TTL per owl:equivalentClass OwlHydrator per-family codebook Reasoner-friendly + + quick-xml + csv models/*.py + module + one CSV/ owl:equivalentProperty with inherits_from: storage (per-OGIT Object Type + Link + parse the source XML walker per XML-derived TTL + per-domain Some(OGIT::DOLCE_ per §4.7) + Lance Type + Action Type + tree without data/*.{xml,csv} per data file alignment files V1.0); inherits- cache persistence + Function Type + running Odoo (odoo→fibo, from chain surface + odoo→ubl, odoo→skr) propagates DOLCE + categories +``` + +### 14.5 Deliverables — Odoo OWL glue as parallel substrate stream + +Re-ranking the Odoo work-steal from `b9531cf3-odoo_work_steal_distillation.md` §"Phased roadmap aligned with the L1-L4 distillation" against the POC priorities: + +| D-id (NEW) | From Odoo distillation Phase | Priority | Description | Effort | +|---|---|---|---|---| +| **D-ODOO-1** | O0 (bootstrap extractor) | **P1** (parallel to POC P0) | `crates/odoo-extract/` skeleton: `manifest.rs` (parse __manifest__.py) + `python_ast.rs` (rustpython-parser AST walker over models/*.py) + `emit_owl.rs` (sophia TTL emitter). Round-1 = `base` module only. | ~1 weekend (1500 LOC) | +| **D-ODOO-2** | O1 (extract `base`) | **P1** (parallel to POC P0) | Run D-ODOO-1 against `base` module → `data/ontologies/odoo/base.ttl`. ~80 fields across `res.partner` / `res.users` / `res.company` / `res.country` / `res.currency` / `res.bank` / `res.lang`. | ~1 evening | +| **D-ODOO-3** | O2 (hand-curate alignment for `base`) | **P1** (parallel to POC P0) | `data/ontologies/odoo/alignment/odoo-to-fibo.ttl` + `odoo-to-vcard.ttl` + `odoo-to-foaf.ttl`. ~100 alignment axioms. LLM-draft-then-reviewed acceptable. | ~1 evening | +| **D-ODOO-4** | (new) | **P1** (parallel to POC P0) | `lance-graph-ontology::hydrators::odoo::hydrate_odoo_base` following the bO-* pattern; `inherits_from: Some(OGIT::DOLCE_V1.0)`; registers edge whitelist; lands at `OGIT::ODOO_BASE_V1`. ~50 LOC + 4 tests. | ~1 day | +| **D-ODOO-5** | O3 (extract `account`) | **P1** (parallel to POC P1) | `odoo/account.ttl` + `alignment/odoo-to-fibo-gl.ttl`. `account.move` ↔ `gl-cor:entryHeader` + `fibo:Transaction` (the dual-nature mapping per §"Important" in the doc). `account.move.line` ↔ `gl-cor:entryDetail` + `fibo:JournalEntryLine`. | ~2 evenings + ~1 day for hydrator | +| **D-ODOO-6** | O4 (extract `l10n_de_skr03` + `l10n_de_skr04`) | **P2** (substrate already covered by `hydrate_skr03` / `hydrate_skr04` from in-tree DATEV CSV) | Refresh path: pull Odoo's `account.account.template.csv` to confirm the in-tree SKR03/04 hasn't drifted. Diff against `data/skr/SKR0[34].csv`. If drift detected, regenerate. | ~1 evening (mostly a diff job) | +| **D-ODOO-7** | (new — for the POC) | **P1** (gated on POC PR-5) | `data/ontologies/odoo/alignment/odoo-to-zugferd.ttl` — align `account.move` (move_type=out_invoice) to ZUGFeRD/Factur-X invoice shape. The PR-5 generator can then attribute the generated XRechnung to the Odoo-typed source row. | ~1 day | +| **D-ODOO-8** | O9 (extract product / sale / purchase / stock) | **P2** (post-POC; broader ERP coverage) | Five more per-module TTLs + alignment files. ~1 weekend per module batch. | ~1 weekend | +| **D-ODOO-9** | O7 (Python adapter for live ingest) | **P2** (post-POC; "two-version bridge" pattern) | `odoo-ada-adapter` package: `as_rdf(record) → rdflib.Graph` for export. Allows live Odoo deployments to feed RDF directly into the lance-graph cognitive substrate. | ~3 evenings | +| **D-ODOO-10** | O8 (end-to-end demo) | **P2** (post-POC; the rewarding end-state) | First end-to-end demo: live Odoo instance → adapter → shared ontology → Rust cognitive cascade. Cognitive shader reasons about a real Odoo `res.partner` row as `fibo:LegalEntity + vcard:Kind`. | ~1 weekend | + +### 14.6 Revised §3 priority ranking — Odoo elevated to P1 + +Updated priority table (revising the §3 row that had Odoo at P3): + +| Priority | Plan / phase | LOC + days | Reasoning | +|---|---|---|---| +| **P0** | woa-rs Phase 0-3 + PR-5 (XRechnung) | ~7-8 days | POC milestone. First customer-visible artefact. | +| **P1** | woa-rs PR-6/7 + MedCare Phase 1-3 + SMB Phase A-B | ~15-20 days | Immediate post-POC iteration + cross-consumer harvest. | +| **P1** (RAISED FROM P3) | **Odoo OWL glue D-ODOO-1 through D-ODOO-5 + D-ODOO-7** (~5 deliverables + alignment with PR-5) | **~1 weekend + ~3 days per deliverable = ~10-12 days net** | **Parallel substrate stream feeding the cognitive models. NOT blocking on POC completion — runs concurrently.** D-ODOO-7 lands gated on PR-5 to attribute generated XRechnung to Odoo-typed source rows. | +| **P2** | All three consumer plans' Phase 4-5 (Cypher / SPARQL / CAM-PQ opt-in) | ~10-18 days | Post-POC opt-in features. | +| **P2** | unified-bridge D-UB-11 cross-consumer parity test | ~120 LOC + 4 tests | Regression gate. | +| **P2** | Odoo D-ODOO-6, D-ODOO-8, D-ODOO-9, D-ODOO-10 | ~3 weekends | Broader Odoo coverage + Python adapter + end-to-end demo. | + +### 14.7 Why this is the most rewarding end-state + +Once D-ODOO-10 lands, the lance-graph cognitive substrate reasons about ERP entities natively. Concretely: + +- **A Cypher query** like `MATCH (c:fibo:LegalEntity)-[:fibo:hasAccount]->(a:fibo:Account) WHERE c.country = 'DE' AND a.skr_konto STARTS WITH '8'` runs across the Lance projection of Odoo-extracted ERP data + DATEV SKR04 chart + FIBO foundations. Three ontology stacks, one query. +- **The NARS inference engine** dispatches different evidence-update rules per OWL type: a contradiction between two `fibo:Counterparty` rows triggers identity-resolution; a contradiction between a `fibo:Transaction` and its `fibo:Account` triggers double-entry validation. +- **The MUL gate** picks `MulThresholdProfile::FINANCIAL` for FIBO-typed reasoning (per the upcoming `super-domain-rbac-tenancy-v1.md` extension), `::MEDICAL` for Healthcare, `::DEFAULT` elsewhere — keyed on the OWL super-domain. +- **The cognitive shader's 16-bit DOLCE slot classifier** has every Odoo entity pre-classified at hydration time (per §4.4's O(1) inheritance from family buckets); the cascade activates with `slot[high_byte=Endurant.Agent, low_byte=DnS.LegalActor]` for every `res.partner` row without runtime classification cost. +- **The Foundry-style typed-object surface** (per `f6b68582-erp_foundry_hhtl_ontology_distillation.md` §"Foundry-style semantic surface") becomes navigable across Odoo's full operational vocabulary: every `res.partner` is an Object Type with typed Link Types (`fibo:hasAccount`, `fibo:hasCounterparty`), Action Types (`BookJournalEntry`, `ReconcileBankStatement`), Function Types (`computeVATPosition`, `runningBalance`). + +The reward isn't "wired correctly to one app" (that's PR-5). The reward is **the cognitive substrate becomes a Palantir-Foundry-class semantic-reasoning system grounded in real ERP semantics**. PR-5 is the proof the wiring works on one route; D-ODOO-10 is the proof the wiring works on the entire ERP semantic surface. + +### 14.8 What this DOES NOT change + +- woa-rs PR-5 stays the POC milestone (§2). Odoo work-steal does not displace the customer-visible reward arc. +- The L1-L4 layered ontology dependency map (§5) stays. Odoo extraction LANDS on top of L1-L4 via `inherits_from: Some(OGIT::DOLCE_V1.0)`; it doesn't replace any L1-L4 hydrator. +- The §4.7 per-OGIT-G-slot storage rule applies: every Odoo per-module TTL lands at its own `OGIT::ODOO__V1` slot. `odoo:base` ≠ `odoo:account` ≠ `odoo:l10n_de_skr04` at the CAM substrate. +- The §4.6 read-only spine governance applies: Odoo extraction emits `MappingProposal` streams via `OntologyRegistry::append_proposals` (per `b9531cf3-odoo_work_steal_distillation.md` §"Extraction methodology" Path A static path); no consumer crate mutates the spine. + +### 14.9 One-line summary + +> The Odoo work-steal is the parallel substrate stream feeding the cognitive models — elevated from P3 (parallel after POC) to **P1 (parallel concurrent with POC)** because the cognitive shader, NARS engine, MUL gate, 16-strategy planner, and CausalEdge64 substrate all reason BETTER per OWL type, and Odoo is the cheapest path to a comprehensive OWL substrate covering 20+ years of ERP semantics. D-ODOO-1 through D-ODOO-5 + D-ODOO-7 ship in parallel with woa-rs PR-1..PR-5. D-ODOO-10 (live Odoo → adapter → cognitive cascade end-to-end) is the rewarding end-state — the proof the substrate reasons about the entire ERP semantic surface, not just one route. + From e6f13ff8941a1c524c32cf0a104797d2a3bc4cbb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 07:34:22 +0000 Subject: [PATCH 19/22] =?UTF-8?q?docs(plans/poc):=20=C2=A715=20=E2=80=94?= =?UTF-8?q?=20Odoo=20=E2=86=94=20Palantir=20Foundry=20convergence=20+=20le?= =?UTF-8?q?ssons=20for=20OGIT/OWL/DOLCE=20+=20WoA=20work-steal=20map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User request: analyse how Odoo OWL and Palantir Foundry ontology converge; what we can learn for the long run with OGIT/OWL/DOLCE; what WoA could wire/repurpose/work-steal/reinvent from Odoo and smb-office-rs. §15 NEW — covers: §15.1 Convergence map (Odoo OWL ↔ Palantir Foundry) — 13-row table showing structural homology at the typed-object surface despite radically different runtimes (Odoo Python+PostgreSQL vs Foundry proprietary Spark-shaped). Both reduce to OWL/SHACL/ SKOS+PROV-O; OGIT is positioned to host both as projections. §15.2 Divergences and what each teaches — 8 axes where they diverge (authoring style, runtime coupling, granularity, versioning, schema evolution, licensing, naming, reasoning depth). Headline lesson: Odoo gives operational coverage at the cost of runtime coupling; Foundry gives loose coupling at the cost of curated authoring. OGIT decouples authoring from coverage by accepting both into one CAM-addressable substrate, plus adds the cognitive layer neither has. §15.3 Six long-run lessons for OGIT/OWL/DOLCE: 1. Typed-object surface IS a projection, not a separate ontology 2. Operational vocabulary needs an extraction source 3. DOLCE-as-root is the structural decision that makes everything compose (16-bit cognitive shader slot is defined relative to DOLCE) 4. Alignment TTL is the "glue"; authoring workflow matters (mechanical for obvious; human-judgement for subtle) 5. Per-OGIT storage + read-only spine + CAM bar codes compose into a self-reinforcing pattern 6. PROV-O is non-optional for any regulated domain §15.4 WoA work-steal map from Odoo — 16-row entity-by-entity table mapping WoA's sea-orm entities to Odoo modules: - Customer ← res.partner + res.partner.bank (WORK-STEAL) - Tenant ← res.company (WORK-STEAL) - User ← res.users + res.partner linked (WORK-STEAL) - WorkOrder ← account.move dual-nature + sale.order (WORK-STEAL the dual-projection pattern) - Position ← account.move.line + sale.order.line (WORK-STEAL) - Mahnung ← (OCA account_due_list or hand-roll) (REPURPOSE) - Stundenzettel-Eintrag ← hr.timesheet / account.analytic.line (WORK-STEAL) - Einsatz ← project.task + helpdesk.ticket + mrp.workorder (REPURPOSE) - Logbook-Eintrag ← mail.thread mixin (WORK-STEAL) - Dokument ← ir.attachment (WORK-STEAL) - Setting / tenant_settings ← ir.config_parameter (REINVENT per RFC-001 — keep typed-struct discipline) - SKR03/SKR04 chart ← l10n_de_skr03/04 (ALREADY WIRED via hydrate_skr03/04) - DATEV Steuerschlüssel ← l10n_de tax templates (PARTIALLY WIRED) - ZUGFeRD/XRechnung generator ← Odoo e-invoicing (ALREADY WIRED via hydrate_zugferd + hydrate_zugferd_rules) - Computed-field dependency graph ← @api.depends (REINVENT as SHACL sh:rule + SPIN) §15.5 WoA work-steal map from smb-office-rs — 14-row artifact-by-artifact table: - woa-bridge / woa-ontology crate skeleton (WIRE 1:1) - woa_unified_bridge constructor (WIRE 1:1 mirror with WoaBridge type-param swap) - WoaMysqlConnector EntityStore impl (REINVENT for sea-orm/ MySQL — trait surface transfers, impl differs from MongoDB) - WoaMysqlReconciler shell (WORK-STEAL shell + reinvent fetchers) - sea-orm-schema-warden agent (REINVENT for sea-orm) - transcode-auditor agent (WIRE with woa-specific reference path) - unified_bridge_wiring.rs design-map doc-comments (WIRE with substitutions) - Iron Rule "lance-graph is additive-only" (WIRE as policy) - Per-customer binary pattern (WIRE LATER — P3, not POC scope) - FFI to WinForms (DO NOT TAKE — WoA has web UI not desktop) - MongoDB connector (DO NOT TAKE — WoA cold path is MySQL) - BSON-schema-specific Customer canonical row (REINVENT with WoA field set) §15.6 The unified roadmap implication — WoA's POC path is mostly composition, not invention. Actual invention surface is ~5 deliverables totalling ~250 LOC: sea-orm fetchers for the reconciler; Mandant.id → TenantId mapping; permission to actor_role mapping; ~9 routes in security-critical buckets that aren't bucket-generic; askama templates for new WoA- specific UI views (e.g., /admin/parity). Everything else is harvest. Explains why the POC achieves in ~7-8 days net. §15.7 One-line summary: typed-object surface IS a projection; operational vocabulary needs an extraction source; DOLCE-as- root is load-bearing; alignment TTL authoring matters; per-OGIT + read-only + CAM compose into self-reinforcement; PROV-O is non-optional. WoA work-steals ~80% from Odoo + smb; actual invention surface ~5 deliverables / ~250 LOC. --- ...-graph-business-logic-poc-via-woa-rs-v1.md | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md index 674c6c04..dc9ceb99 100644 --- a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md +++ b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md @@ -362,3 +362,139 @@ The reward isn't "wired correctly to one app" (that's PR-5). The reward is **the > The Odoo work-steal is the parallel substrate stream feeding the cognitive models — elevated from P3 (parallel after POC) to **P1 (parallel concurrent with POC)** because the cognitive shader, NARS engine, MUL gate, 16-strategy planner, and CausalEdge64 substrate all reason BETTER per OWL type, and Odoo is the cheapest path to a comprehensive OWL substrate covering 20+ years of ERP semantics. D-ODOO-1 through D-ODOO-5 + D-ODOO-7 ship in parallel with woa-rs PR-1..PR-5. D-ODOO-10 (live Odoo → adapter → cognitive cascade end-to-end) is the rewarding end-state — the proof the substrate reasons about the entire ERP semantic surface, not just one route. + +--- + +## 15 — Odoo ↔ Palantir Foundry convergence + lessons for OGIT/OWL/DOLCE + WoA work-steal map (2026-05-21, same session) + +User request: analyse how Odoo OWL and Palantir Foundry ontology converge; what we can learn for the long run with OGIT/OWL/DOLCE; what WoA could wire / repurpose / work-steal / reinvent from Odoo and smb-office-rs. + +### 15.1 Where Odoo OWL and Palantir Foundry converge + +The two systems are structurally homologous at the **typed-object semantic surface** despite radically different runtime models. The convergence is the proof OGIT can subsume both: a single OWL substrate addresses the same conceptual entities they both manipulate, with their respective surfaces becoming projections over it. + +| Convergence axis | Odoo (Python ERP) | Palantir Foundry | Shared substrate (OGIT/OWL/DOLCE) | +|---|---|---|---| +| **Entity typing** | Each `models.Model` subclass with `_name = 'res.partner'` defines a class | Each "Object Type" with declared properties + primary key | `owl:Class` (declared at hydration time) + `OwlIdentity` bar code (CAM addressing) | +| **Field typing** | `fields.Char` / `fields.Many2one` / `fields.Selection` etc. (datatype + relation discriminator) | DatatypeProperty + ObjectProperty distinction; primary-key declaration | `owl:DatatypeProperty` (with `rdfs:range` to xsd:*) vs `owl:ObjectProperty` (with `rdfs:domain`/`rdfs:range` to OWL classes) | +| **Inheritance** | `_inherit = 'res.partner'` (dynamic Python class composition) | Foundry Object Type inheritance + interface-like extension | `rdfs:subClassOf` chain + `inherits_from` G-slot DAG (per §4.4 O(1) inheritance) | +| **Relationships** | `Many2one` / `One2many` / `Many2many` field declarations | Link Type with declared cardinality + inverse | `owl:ObjectProperty` + `owl:inverseOf` + (optional) `owl:FunctionalProperty` cardinality marker | +| **Constraints** | `@api.constrains('field')` (Python validation) + `_sql_constraints` (DB) | SHACL pre/post on Action Types | SHACL `sh:NodeShape` + `sh:property` declarations alongside the OWL T-Box | +| **Computed/derived properties** | `fields.X(compute='_compute_foo')` + `@api.depends(...)` dependency graph | Function Types (named SPARQL bound to function URI) | SHACL `sh:rule` + SPIN/SHACL Functions; or named SPARQL projections published as `owl:DatatypeProperty` | +| **Provenance / audit** | `mail.thread` mixin tracks who-changed-what-when on every record | Foundry Audit Trail attached to every Object/Action | PROV-O (already shipped via `hydrate_provo`); per-row `prov:wasAttributedTo` + `prov:wasGeneratedBy` + `prov:wasDerivedFrom` | +| **Localization / i18n** | `_description` + `string="..."` on fields + `ir.translation` table | Foundry localized labels per Object Type | `rdfs:label@` + `rdfs:comment@` (German + English baseline via existing hydrators) | +| **Selection / enum types** | `fields.Selection([('person', 'Individual'), ...])` | Enum types attached to Foundry properties | SKOS Concept Scheme (`hydrate_skos` already ships); one Concept per selection option | +| **Action / workflow** | Odoo workflow engine + button handlers | Action Types with SHACL pre/post + SPARQL UPDATE templates | SHACL `sh:rule` + `dul:Plan` (DOLCE+DnS DnS Plan classification) — already preserves Action semantics in the L1 substrate | +| **Source-of-truth identity** | `res.partner.id` (PostgreSQL primary key) | Foundry Object Locator (system-generated stable URI) | `OwlIdentity` u16 bar code (per §4.5 CAM substrate) + OGIT URI alias for human-readable reference | +| **Reference data (A-Box)** | `data/*.xml` + `data/*.csv` loaded at module install | Foundry Reference Data Sets | `MappingProposal` stream into `OntologyRegistry`; Lance dataset persistence under `lance-cache` feature | +| **Per-tenant scope** | Multi-company sharing semantics + `res.company` discriminator | Foundry project scoping + access control | `NamespaceBridge.g_lock()` + `UnifiedBridge.authorize()` 4-stage flow (per super-domain-rbac-tenancy-v1.md §3.9) | + +**The convergence is the proof.** When two systems with proprietary divergent runtimes (Odoo Python+PostgreSQL vs Foundry proprietary Spark-shaped backend) both reduce to the same OWL/SHACL/SKOS+PROV-O substrate, that substrate IS the universal semantic layer. OGIT is positioned to host both as projections — Odoo's as an extracted ontology + Python adapter (per the work-steal distillation); Foundry's as the typed-object projection surface per `f6b68582-erp_foundry_hhtl_ontology_distillation.md` §"Foundry-style semantic surface". + +### 15.2 Where they diverge — and what each divergence teaches + +| Divergence axis | Odoo posture | Foundry posture | What OGIT/OWL/DOLCE inherits | +|---|---|---|---| +| **Ontology authoring** | **Implicit** — emerges from Python source; extraction is post-hoc reverse engineering | **Explicit** — Ontology Manager UI; authored upfront by data engineers | **Hybrid.** L1-L4 hydrators (DOLCE/PROV-O/QUDT/FIBO/etc.) are upfront-authored standards; `hydrate_odoo_*` extracts post-hoc. Both feed the same `OntologyRegistry`. The OGIT spine accommodates both authoring styles because the producer-side (per §4.6) is single — `MappingProposal` regardless of source. | +| **Coupling to runtime** | **Tight** — Odoo classes inherit Odoo's full ORM stack; can't extract Partner without inheriting `mail.thread` etc. | **Loose** — Object Types are runtime-independent; same Object Type can power a Foundry workflow + a REST API + a Spark job | OGIT inherits the **loose-coupling** discipline. `lance-graph-ontology` ships the spine; consumer crates (woa-bridge, medcare-bridge, smb-bridge) bind to it without contaminating it. The §4.6 read-only spine + §4.7 per-OGIT storage are the structural enforcement of this. | +| **Granularity of typing** | **Operational** — every field is in the data model; ~50k modules cover near-everything | **Curated** — Object Types are designed for analytic use; not auto-generated from raw rows | OGIT supports **both**. Odoo work-steal gives operational coverage; manual L3 hydrators (FIBO-FND/BE, schema.org subsets) give curated coverage. The cognitive substrate doesn't care; it reasons over both via the same CAM bar-code address space. | +| **Versioning** | **Module-version + Odoo-release-version** (annual major bumps; module deps pin per-release) | **Foundry semantic versioning** per Object Type | OGIT inherits the **versioned-G-slot** pattern (per §4.6's `OGIT::*_V1.0` (slot, version) tuple). Migration is consumer-driven opt-in; V1 and V2 coexist physically separate per the per-OGIT-storage rule. | +| **Schema evolution** | **Migration scripts per Odoo upgrade** (often hand-authored) | **Foundry ontology evolution** with backward-compat layers | OGIT inherits the **hydrate-once-per-version, version-coexist** model. New hydrator version (`hydrate_dolce_v2`) lands alongside `hydrate_dolce_v1`; consumers migrate when they pin to V2. | +| **Licensing posture** | **LGPL/OEEL split** — community is extractable; enterprise modules are not | **Proprietary** — no extraction path; only API consumption | OGIT must respect both. Odoo extraction stays LGPL-only per the work-steal doc; Foundry consumption is API-only. The OGIT spine is the public-good substrate both feed into. | +| **Naming conventions** | **Python identifiers** (`res.partner`, `account.move.line`) — Odoo-internal | **Domain-driven names** (`Counterparty`, `Position`, `JournalEntryLine`) — business-facing | OGIT names use **canonical OGIT URIs** (`ogit.Network:IPAddress`, `ogit.WorkOrder:Customer`, `ogit.Finance:Transaction`). Odoo names alias via `owl:equivalentClass`; Foundry-style names project via SPARQL. Same substrate, multiple aliases. | +| **Reasoning depth** | **Limited** — Odoo's reasoner is the Python eval of computed fields | **Moderate** — Foundry's reasoner is the typed-object surface + ad-hoc Spark queries | **Deep** — OGIT/lance-graph adds the cognitive substrate (NARS, MUL, DOLCE 16-bit slot classifier, 16-strategy planner). The convergence's structural gap (neither Odoo nor Foundry has a full cognitive layer) IS lance-graph's differentiation. | + +**Headline lesson from the divergences:** Odoo gives us operational coverage at the cost of runtime coupling; Foundry gives us loose coupling at the cost of curated authoring. OGIT/lance-graph **decouples authoring from coverage** by accepting both upfront-authored standards (L1-L4 hydrators) and post-hoc extracted vocabularies (Odoo work-steal) into one CAM-addressable substrate, then adds the cognitive layer neither competitor has. + +### 15.3 Long-run lessons for OGIT/OWL/DOLCE + +Six structural lessons the convergence teaches, ordered by load-bearing-ness: + +1. **The typed-object surface is a projection, not a separate ontology.** Foundry's Object/Link/Action/Function types map 1:1 to OWL primitives per the third distillation doc §"Foundry-side semantic surface". OGIT must NOT introduce a second ontology layer for the "user-facing typed object" view — it's a SPARQL/SHACL projection over the OWL substrate. Concrete consequence: the future `crates/foundry-projection/` (if ever built) is a thin SPARQL-template emitter, not a competing class hierarchy. +2. **Operational vocabulary needs an extraction source; pure formal-ontology authoring is insufficient.** Re-modelling 20 years of ERP from FIBO+UBL+XBRL-GL alone is years of work that Odoo already did. The hydrator pattern (per §4.3) generalizes to "any source with formal-enough structure": Python ORMs (Odoo), XSD schemas (UBL/ZUGFeRD), CSV reference data (DATEV SKR), legal text (HGB term extraction). Build the extractors; don't try to author the whole vocabulary by hand. +3. **DOLCE-as-root is the structural decision that makes everything else compose.** Every L2/L3/L4 hydrator declares `inherits_from: Some(OGIT::DOLCE_V1.0)` (per §4.3 layered ontology table). The 16-bit cognitive-shader DOLCE slot is *defined* relative to DOLCE's upper categories (Endurant/Perdurant/Quality/Abstract). Without DOLCE, downstream hydrators have no shared root — they classify against incompatible upper ontologies (BFO vs DOLCE vs SUMO) and the cognitive reasoner can't dispatch uniformly. DOLCE was the right L1 pick; keep it. +4. **The alignment TTL is the "glue", and its authoring workflow matters.** Per `b9531cf3-odoo_work_steal_distillation.md` §"Naming convention", alignment files (`owl:equivalentClass`, `owl:equivalentProperty`, `owl:sameAs`) are what tie extracted vocabularies to upstream standards. Authoring is mostly mechanical for the obvious cases (`res.partner.name owl:equivalentProperty foaf:name`) but human-judgement-required for the subtle cases (`account.move` IS BOTH `gl-cor:entryHeader` AND `fibo:Transaction` AND `ubl:Invoice` depending on `move_type` discriminator). The workflow has to support both: LLM-draft-then-reviewed for the obvious, human-authored for the subtle. Per the work-steal doc §"Open questions" — this is an open question worth answering before D-ODOO-3. +5. **Per-OGIT storage (§4.7) + read-only spine (§4.6) + CAM bar codes (§4.5) compose into a self-reinforcing pattern.** Each per-source TTL lands at its own `OGIT::_V1` G-slot (Odoo `base` ≠ Odoo `account` ≠ FIBO-FND ≠ DOLCE). Per-G-slot CAMs are independently versionable + invalidatable + persistent. The bar-code addressing scheme + the inherits-from chain + the dense per-family arrays + the controlled producer-side appender = a substrate that scales to "everything anyone has ever ontologized" without thundering cache invalidation. This is the structural property that justifies building cognitive models around the substrate — the substrate doesn't drift under load. +6. **PROV-O is non-optional for any regulated domain.** GoBD (German digital bookkeeping), HIPAA (US healthcare), SOX (US financial reporting), GDPR (EU privacy), MiFID II (EU financial markets) — every regulated regime requires an unbroken provenance chain from output to source. Per the third distillation doc §"PROV-O" entry: "every entity in the ontology needs `prov:wasGeneratedBy`, `prov:wasDerivedFrom`, `prov:wasAttributedTo` to satisfy the German bookkeeping audit trail requirement." Both Odoo (via mail.thread mixin) and Foundry (via audit trail) ship this; OGIT already has `hydrate_provo` for it. Lesson: don't carry data through any layer that strips PROV-O — including the cognitive layer (CausalEdge64 v2's W-slot is the PROV-O carrier per `cognitive-substrate-convergence-v1.md`). + +**The meta-lesson:** the convergence of Odoo + Foundry around an OWL substrate isn't accidental. Both arrived at the same shape by independent paths because the shape is structurally inevitable for any system that needs to **reason over typed business entities with regulatory auditability**. OGIT/lance-graph is positioned to be the open canonical implementation of that shape — and the cognitive substrate is the differentiator neither competitor has. + +### 15.4 WoA work-steal map from Odoo + +Direct correspondences between Odoo modules and WoA's existing sea-orm entities, with the wire/repurpose/work-steal/reinvent classification: + +| WoA entity | Odoo source | Action | Notes | +|---|---|---|---| +| Customer (`Kunde`) | `res.partner` + `res.partner.bank` | **WORK-STEAL** | ~80 Odoo fields cover identity / address / contact / banking / tax IDs / company-vs-individual discriminator. WoA's Customer has ~25 fields; absorb the missing ~55 (or at least the alignment axioms) via `hydrate_odoo_base`. **D-ODOO-2 deliverable.** | +| Tenant (`Mandant`) | `res.company` | **WORK-STEAL** | Odoo's company master with chart-of-accounts binding maps directly to WoA's Mandant. Especially the `currency_id` / `country_id` / `chart_template_id` cross-links. | +| User (`Benutzer`) | `res.users` + `res.partner` (linked) | **WORK-STEAL** | Odoo's two-table user model (Partner-base + User-overlay) is cleaner than WoA's flat User table; the alignment via PROV-O `prov:Agent + foaf:Person` lets the cognitive shader reason about user-authored actions uniformly. | +| WorkOrder (`Vorgang`) | `account.move` (dual nature: journal entry + invoice) + `sale.order` | **WORK-STEAL** (the dual-projection pattern) | Odoo's `move_type` discriminator (`out_invoice`, `in_invoice`, `out_refund`, `in_refund`, `entry`) maps to WoA's Vorgang `kind` (Angebot / Auftragsbestätigung / Lieferschein / Rechnung / Gutschrift). Emit BOTH `fibo:Transaction` AND `ubl:Order|Invoice` per record — the cognitive layer can reason about either projection. | +| Position (`Position`) | `account.move.line` + `sale.order.line` | **WORK-STEAL** | Odoo's move-line model with per-line tax_ids + analytic accounting + product reference is the right shape for WoA Position. The `tax_ids` cross-link via `account.tax` connects to DATEV Steuerschlüssel naturally. | +| Mahnung (`Mahnung`) | (no direct Odoo equivalent in standard modules; OCA `account_due_list` or hand-roll) | **REPURPOSE** | Mahnwesen (German dunning) is German-specific; Odoo's community modules cover it via OCA. Repurpose the `account.payment.term` structure + escalation rules; reinvent the 3-stage Mahnstufe escalation in woa-bridge. | +| Stundenzettel-Eintrag | `hr.timesheet` (`account.analytic.line` in modern Odoo) | **WORK-STEAL** | Odoo's analytic-line + 15-minute rounding patterns match WoA's Stundenzettel Takt-15. Wire via `hydrate_odoo_hr` (P2 / D-ODOO-8). | +| Einsatz | `project.task` + `helpdesk.ticket` (community) + `mrp.workorder` | **REPURPOSE** | Multiple Odoo modules cover the live on-site engagement concept; none are perfect. Cherry-pick the time-tracking + photo-capture + signed-checkout fields from the OCA `project_task_signature` community module. | +| Logbook-Eintrag | `mail.thread` mixin records | **WORK-STEAL** | Odoo's mail.thread is exactly the audit-trail shape WoA's Logbook needs. PROV-O alignment via the work-steal doc §"Cross-cutting Odoo concerns to extract" → `mail.thread` row. | +| Dokument | `ir.attachment` | **WORK-STEAL** | Odoo's document-binary linkage model is well-tested; WoA's Dokument can absorb the same shape including the per-record binary refs + MIME-type discriminators. | +| Setting / `tenant_settings` | `ir.config_parameter` + `res.config.settings` | **REINVENT** | Per RFC-001 (`tenant_settings` typed struct), WoA explicitly diverged from Python's KeyValue Setting. Don't re-absorb Odoo's key-value pattern; keep the typed-struct discipline. | +| SKR03/SKR04 chart of accounts | `l10n_de_skr03` + `l10n_de_skr04` modules | **ALREADY WIRED** | `hydrate_skr03` + `hydrate_skr04` ship in lance-graph-ontology post-PR-407. **D-ODOO-6 is a P2 diff/refresh job**, not new work. | +| DATEV Steuerschlüssel | `l10n_de` tax templates (`account.tax.template.xml`) | **ALREADY WIRED PARTIALLY** | The chart is in tree; the Steuerschlüssel-to-USt-line mapping (`l10n_de_tax_statement`) is a Phase O5 follow-up worth pulling. **P2.** | +| ZUGFeRD/XRechnung invoice generator | (Odoo's e-invoicing module + l10n_de_zugferd community) | **ALREADY WIRED** | `hydrate_zugferd` + `hydrate_zugferd_rules` ship in lance-graph-ontology post-PR-407. The POC PR-5 milestone consumes these. **D-ODOO-7 adds the alignment axioms tying `account.move` → ZUGFeRD invoice shape.** | +| `mail.thread` audit trail | Same | **WORK-STEAL** | Already noted above for Logbook; reused here because it's also the substrate WoA needs for every entity's PROV-O annotation. One Odoo concept, many WoA consumers. | +| Computed-field dependency graph | `@api.depends(...)` decorators | **REINVENT** as SHACL | WoA's invoice totals + Mahnwesen escalation triggers + tax-base calculations all follow Odoo's compute-on-depends shape. Reinvent as SHACL `sh:rule` + SPIN; emits the same shape but lance-graph-native rather than Python-eval. | + +**Headline win:** D-ODOO-2 + D-ODOO-3 + D-ODOO-4 (extract Odoo `base` module + alignment + `hydrate_odoo_base`) gives WoA's Customer / Tenant / User entities the 20-year-validated Odoo Partner field set + alignment axioms to FIBO/vcard/foaf — directly addressing the §10.5 D-WLG-15 `CanonicalCustomerRow` deliverable. The WoA reconciler diffs a richer canonical row than it would by re-deriving fields from scratch. + + +### 15.5 WoA work-steal map from smb-office-rs + +smb-office-rs and WoA are sister consumers — both ports of inherited legacy systems, both subject to the unified-bridge consumer pattern, both end up reading the same lance-graph-ontology spine. But smb sources from C# WinForms + MongoDB; WoA sources from Python Flask + MySQL. Direct correspondences: + +| WoA artifact | smb-office-rs source | Action | Notes | +|---|---|---|---| +| `crates/woa-bridge/` crate skeleton | `crates/smb-bridge/` | **WIRE** (mirror structure 1:1) | Per `lance-graph-in-woa-rs-v1.md` §10 — the smb-bridge crate layout (mod batch / error / number_sequence / orchestration / rls / settings / wal) is the template. Wire same shape into woa-bridge. | +| `crates/woa-ontology/` crate skeleton | `crates/smb-ontology/` | **WIRE** (mirror structure 1:1) | Same; smb's customer.rs / mahnung.rs / markings.rs pattern maps to woa-ontology/{customer,workorder,position,setting,user,document}.rs. | +| `woa_unified_bridge(...)` constructor | `smb_unified_bridge(...)` in `unified_bridge_wiring.rs` (~90 LOC) | **WIRE** (parameterise over WoaBridge instead of OgitBridge) | Per `lance-graph-in-smb-office-rs-v1.md` §7 — smb is the canonical template source for this constructor across all consumer plans. **D-UB-4 / D-WLG-3 implementation = 1:1 mirror with type-param swap to WoaBridge.** | +| `WoaMysqlConnector` impl of `EntityStore + EntityWriter` | `smb-bridge::mongo::MongoConnector` (313 LOC, gated `[features] mongo`) | **REINVENT** for sea-orm/MySQL | The trait surface (`lance-graph-contract::repository::{EntityStore, EntityWriter}`) transfers; the implementation differs (sea-orm Entity queries vs MongoDB BSON cursor iter). ~300 LOC + ~6 tests mirroring smb-bridge's shape. | +| `WoaLanceConnector` impl of same | `smb-bridge::lance::LanceConnector` | **WIRE** (essentially identical) | The Lance-side EntityStore implementation is storage-agnostic; reuse the smb-bridge structure for the Lance projection of woa MySQL tables. | +| `WoaMysqlReconciler` | `SmbMongoReconciler` (395 LOC, mirrors `MedcareMysqlReconciler` per `lance-graph-in-medcare-rs-v1.md` §8) | **WORK-STEAL** the shell + reinvent the fetchers | The reconciler shell (route parser + diff machinery + DriftEvent emission via the `lance_graph_callcenter::transcode::parallelbetrieb::Reconciler` trait) transfers verbatim. Only `CustomerFetcher` impl differs — sea-orm `Entity::find_by_id` vs MongoDB `MongoConnector::find_by_id`. Per §10.5 D-WLG-15. | +| `sea-orm-schema-warden` agent | `mongo-schema-warden` agent (smb's `.claude/agents/`) | **REINVENT** for sea-orm | smb's agent enforces BSON field name parity to C# `db_*.cs`. WoA's analogue enforces sea-orm Entity field name parity to Python `models.py` (per Iron Rule №2 — read Python source before writing Rust). ~200-line agent card mirroring smb's pattern. | +| `transcode-auditor` agent | smb-office-rs's `transcode-auditor` | **WIRE** (reuse verbatim with WoA-specific reference path) | smb's transcode-auditor fires when porting `db_*.cs` files; WoA's analogue fires when porting `woa/.py` route blueprints. Same agent shape, different reference root. | +| `unified_bridge_wiring.rs` doc-comment design map | smb-bridge `unified_bridge_wiring.rs` lines 9-14 + 16-25 + 27-33 | **WIRE** (carry over verbatim with substitutions) | smb's forward-looking comments about the SmbBridge type-parameter swap + auth::TenantId consolidation + post-D-SDR-2/3 shrink are templated thinking. WoA's `woa_unified_bridge` ships with analogous comments substituting `WoaBridge` for `SmbBridge` and `Mandant.id` for `praxis_id`/`kdnr`. | +| Iron Rule "lance-graph is additive-only" | smb-office-rs CLAUDE.md Iron Rule 3 | **WIRE as policy** | WoA inherits this implicitly via being a consumer of `lance-graph-ontology` (the same upstream). Make it explicit in woa-rs CLAUDE.md as a stack-decision row. | +| Per-customer binary pattern | smb-office-rs's `customer--bin` crate pattern (per smb CLAUDE.md "Single binary per customer") | **WIRE LATER** (P3, not POC scope) | Currently WoA is Stefan-single-tenant; multi-tenant deployments would absorb smb's per-customer cargo-feature subsetting model. Defer until WoA actually has multi-tenant deployment demand. | +| FFI to WinForms | smb-bridge's JSON-over-C-ABI FFI | **DO NOT TAKE** | WoA has a web UI (axum + askama), not a desktop UI. FFI is unnecessary and would add complexity. | +| MongoDB connector (smb-mongo + smb-bridge::mongo) | Same | **DO NOT TAKE** | WoA's cold path is MySQL via sea-orm per the DualSink-Pivot. MongoDB is irrelevant. Reinvent the EntityStore/EntityWriter for sea-orm instead (above row). | +| BSON-schema-specific Customer canonical row | smb's `CanonicalCustomerRow` | **REINVENT** with WoA field set | smb's row has German BSON field names (kdnr, firma, vorname, lastname, plz, ort, strasse); WoA's row uses German MySQL field names (kdnr, firma, vorname, nachname, plz, ort, strasse) — similar shape, different storage. Reinvent. | + +**Headline wins from smb work-steal:** +1. **Constructor mirror.** D-WLG-3 (`woa_unified_bridge` constructor) is a 1:1 mirror of `smb_unified_bridge` with `WoaBridge` substituted for `OgitBridge`. ~50 LOC + 2 tests. This is the cheapest possible deliverable in the entire POC P0 path. +2. **Reconciler shell harvest.** The cross-source comparison shell (`SmbMongoReconciler` ← copies `MedcareMysqlReconciler` ← cited as the canonical sister) transfers cleanly to `WoaMysqlReconciler`. Only the `CustomerFetcher` impl is per-consumer. +3. **Agent ensemble templates.** smb's `mongo-schema-warden` + `transcode-auditor` + `truth-architect` + `integration-lead` agent cards transfer with reference-path substitutions. WoA gets a working agent ensemble without designing one from scratch. + +### 15.6 The unified roadmap implication + +The §15.4 (Odoo) + §15.5 (smb-office-rs) work-steal maps + the §14 Odoo OWL glue elevation collapse into one observation: + +**WoA's POC path is mostly composition, not invention.** Specifically: + +- The substrate (lance-graph-ontology spine, hydrators, unified-bridge, parallelbetrieb reconciler) is shipped or in-flight from sister plans (lance-graph + medcare-rs + smb-office-rs). +- The Odoo work-steal supplies the operational ERP vocabulary (~80-field Customer + dual-nature Vorgang/Transaction + chart of accounts + tax structures + audit trail) that WoA's entities map onto. +- The smb-office-rs work-steal supplies the consumer-crate scaffolding (woa-bridge / woa-ontology shape + unified-bridge constructor + reconciler shell + agent ensemble). +- The codegen pipeline (RFC v02-006) propagates each integration step to the 660 routes via per-bucket template edits — one PR upgrades hundreds of route handlers. +- The Foundry-style typed-object surface is a SPARQL projection over the OWL substrate; not a separate ontology layer to build. + +**What WoA actually has to invent:** +1. The sea-orm fetchers for `WoaMysqlReconciler` (per §15.5 D-WLG-16; ~50 LOC of `Entity::find_by_id` per entity). +2. The `Mandant.id` (i32) → `lance_graph_callcenter::TenantId` (u32) mapping (per §3 D-WLG-6; ~40 LOC + 2 tests). +3. The permission-to-actor_role mapping (per §3 D-WLG-7; ~50 LOC + 4 tests covering each WoA permission). +4. The WoA-specific route handlers that aren't bucket-generic (the ~9 routes in the `signed_link_action` + `sa_admin_view` security-critical buckets per RFC v02-006). +5. The askama templates for any new WoA-specific UI views (e.g., `/admin/parity` for PR-6; the codegen handles the route-handler side but the template body is per-bucket-template). + +**That's it.** Everything else is harvest. The 6-month lance-graph substrate work + the smb/medcare consumer-plan work + the Odoo work-steal + the RFC v02-006 codegen pipeline collectively cover ~90% of what WoA's POC needs. WoA's actual invention surface is ~5 deliverables totalling ~250 LOC + ~10 tests. + +This is what makes the POC achievable in ~7-8 days net per §12.6 — the substrate has been pre-built to the point where the customer-visible integration is mostly wiring. + +### 15.7 One-line summary + +> Odoo and Palantir Foundry converge on a typed-object semantic surface that reduces to OWL/SHACL/SKOS+PROV-O; OGIT/OWL/DOLCE is positioned to host both as projections (Odoo via extraction + alignment; Foundry via SPARQL/SHACL projection). The long-run lessons are six: typed-object surface IS a projection not a separate ontology; operational vocabulary needs an extraction source; DOLCE-as-root is the load-bearing structural decision; alignment TTL authoring workflow matters; per-OGIT-storage + read-only-spine + CAM-bar-codes compose into a self-reinforcing pattern; PROV-O is non-optional for any regulated domain. WoA work-steals ~80% of its Customer + Tenant + User + Vorgang + Position + Logbook + Dokument entity shapes from Odoo's `base` + `account` + `mail.thread` modules; ~80% of its consumer-crate scaffolding (woa-bridge, woa-ontology, unified-bridge constructor, reconciler shell, agent ensemble) from smb-office-rs's templates. The actual invention surface is ~5 deliverables, ~250 LOC. The 6 months of lance-graph substrate + sister-consumer-plan harvest + Odoo OWL glue + RFC v02-006 codegen collectively cover ~90% of what WoA's POC PR-5 needs. + From f08e0d71313ee4e8cb55874e2585d4f93b579fb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 08:00:39 +0000 Subject: [PATCH 20/22] =?UTF-8?q?docs(plans/poc):=20=C2=A716=20=E2=80=94?= =?UTF-8?q?=20WoA=20Python=20original=20integrates=20Odoo=20via=20cheap=20?= =?UTF-8?q?ontology=20schema=20lookup=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User question: how should the WoA Python original proceed when integrating Odoo, while aligning with the plans — especially around a "cheap Odoo Ontology Schema lookup store"? Architecture: build-time-extracted Python lookup module (woa-ontology-lookup) that mirrors the TTL files Rust's lance-graph-ontology::OntologyRegistry already consumes. Same TTL source feeds both languages; each gets a native reader; no Odoo runtime dependency on either side. §16 NEW — covers: §16.1 Architecture diagram showing the same TTL files in lance-graph/data/ontologies/ feeding both the Rust hydrators (per PR-407/408) and a new Python build script that emits a flat _data.py module with module-level constants. The Python lookup module is READ-ONLY DATA generated at build time; never parses TTL at request time. §16.2 Cost table for "cheap": O(1) dict access; ~1-5 MB resident; one pip install dep; ZERO additional runtime deps; ~5-30s build-time CPU. Stefan's deployment gets one pip install. No new services / ports / workers. §16.3 Four-phase Python rollout (~5-6 days total): - P1 bootstrap (~1 day): woa-ontology-lookup package skeleton + build_lookup.py consuming OGIT WoA + DOLCE + PROV-O + QUDT TTL files (already shipped post-PR-407). - P2 Odoo integration (~1 day, gated on D-ODOO-2/3 Rust-side): extend build script to consume Odoo base extraction + alignment files. lookup adds odoo_equivalent field. - P3 runtime integration (~1-2 days, gated on P2): add helper methods to WoA models; REST API responses include JSON-LD @context; structured logging carries ontology URIs. - P4 federation surface (~2 days, gated on P3): /api// /rdf endpoints emit Turtle/JSON-LD/N-Triples via rdflib; optional /sparql endpoint. §16.4 Concrete Python package shape: dataclass OntologyMapping; lookup(table, column) → Optional[OntologyMapping]; module-level LOOKUP, TABLE_TO_OGIT_URI, OGIT_URI_TO_FIBO, CONTEXT_JSONLD constants in _data.py. ~250 entries covering all WoA entities. §16.5 Ownership map: - lance-graph (this repo): source-of-truth TTL + python/woa_ontology_lookup/ sub-tree with build script; publishes versioned package. - WoA Python: single requirements.txt line; pure consumer. - woa-rs: already consumes via OntologyRegistry. Both languages are native readers of one source of truth. §16.6 Payoffs for WoA Python BEFORE Rust port replaces anything: 1. Schema-conscious API responses (JSON-LD @context) 2. Odoo parity drift checks at build time 3. Federation via standard linked-data tools 4. Schema-drift detection earlier in pipeline than WoaMysqlReconciler catches it 5. Cognitive-model preparation: Python exports via federation surface; Rust cognitive cascade ingests; two-version bridge from Odoo distillation §"The two-version bridge". §16.7 What this DOES NOT do: - Does NOT change WoA's data model (Iron Rule №5 preserved) - Does NOT add Odoo runtime deps (no odoo-bin, XML-RPC, ORM) - Does NOT introduce reasoning into Python (no OWL reasoner, no SPARQL engine, no SHACL validator at runtime) - Does NOT compete with woa-rs §16.8 Cross-references to §14.5 D-ODOO-1..10, §4.2/4.6/4.7 governance, the Odoo work-steal distillation §"The two-version bridge", and the woa-rs/CLAUDE.md Iron Rules envelope. The constraint envelope: WoA Python stays canonical (Iron Rule №2); doesn't break the source repo (Iron Rule №5); gets schema awareness via build-time extraction; ships independently of the Rust port progress; benefits Stefan even before woa-rs PR-5 lands. --- ...-graph-business-logic-poc-via-woa-rs-v1.md | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md index dc9ceb99..236b6153 100644 --- a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md +++ b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md @@ -498,3 +498,192 @@ This is what makes the POC achievable in ~7-8 days net per §12.6 — the substr > Odoo and Palantir Foundry converge on a typed-object semantic surface that reduces to OWL/SHACL/SKOS+PROV-O; OGIT/OWL/DOLCE is positioned to host both as projections (Odoo via extraction + alignment; Foundry via SPARQL/SHACL projection). The long-run lessons are six: typed-object surface IS a projection not a separate ontology; operational vocabulary needs an extraction source; DOLCE-as-root is the load-bearing structural decision; alignment TTL authoring workflow matters; per-OGIT-storage + read-only-spine + CAM-bar-codes compose into a self-reinforcing pattern; PROV-O is non-optional for any regulated domain. WoA work-steals ~80% of its Customer + Tenant + User + Vorgang + Position + Logbook + Dokument entity shapes from Odoo's `base` + `account` + `mail.thread` modules; ~80% of its consumer-crate scaffolding (woa-bridge, woa-ontology, unified-bridge constructor, reconciler shell, agent ensemble) from smb-office-rs's templates. The actual invention surface is ~5 deliverables, ~250 LOC. The 6 months of lance-graph substrate + sister-consumer-plan harvest + Odoo OWL glue + RFC v02-006 codegen collectively cover ~90% of what WoA's POC PR-5 needs. + +--- + +## 16 — WoA Python original: Odoo integration via cheap ontology schema lookup store (2026-05-21, same session) + +User question: how should the WoA Python original proceed when integrating Odoo, while aligning with the plans — especially around a "cheap Odoo Ontology Schema lookup store"? + +**Constraint reminder.** WoA Python (`AdaWorldAPI/WoA`, Stefan's deployment at localhost + Railway) is **canonical**: per `woa-rs/CLAUDE.md` Iron Rule №2, "Python is the spec; behavioural parity is the spec," and Iron Rule №5, "do not break the source repo." Stefan's Python WoA runs in production; the Rust port mirrors it. So Python-side Odoo integration must NOT change WoA's data model, NOT add Odoo runtime dependencies (`odoo-bin`, XML-RPC, ORM imports), NOT break Stefan's deployment, and MUST stay parity-safe so the Rust port can keep mirroring. + +The right architecture: a **build-time-extracted Python lookup module** that gives Python WoA the same ontology awareness `lance-graph-ontology::OntologyRegistry` gives the Rust side at runtime. Same TTL source feeds both; each language gets a native reader; no Odoo runtime dependency on either side. + +### 16.1 Architecture — the build-time-extracted Python lookup module + +``` +SOURCE (shared across both languages) +───────────────────────────────────── +lance-graph/data/ontologies/ ← upstream OWL/TTL artifacts (post-PR-407) + dul.ttl, owltime.ttl, provo.ttl, qudt.ttl, schemaorg.ttl, skos.ttl, + fibo-fnd.ttl, fibo-be.ttl, zugferd-en16931.ttl, ... +lance-graph/data/ontologies/odoo/ ← Odoo extraction output (D-ODOO-2) + base.ttl, account.ttl, l10n_de.ttl, l10n_de_skr03.ttl, l10n_de_skr04.ttl +lance-graph/data/ontologies/odoo/alignment/ ← hand-curated alignment TTL (D-ODOO-3) + odoo-to-fibo.ttl, odoo-to-vcard.ttl, odoo-to-foaf.ttl, odoo-to-ubl.ttl +lance-graph/data/ontologies/woa/ ← WoA-specific alignment (NEW) + woa-to-odoo.ttl, woa-to-fibo.ttl, woa-to-ogit.ttl + + │ + ▼ +RUST CONSUMER (lance-graph-ontology) PYTHON CONSUMER (NEW — woa-ontology-lookup) +───────────────────────────────────── ──────────────────────────────────────────── +hydrate_dolce + hydrate_provo + ... build_lookup.py script: + ↓ parses the same TTL files +OntologyRegistry @ per-OGIT G-slots + alignment files + ↓ + WoA's models.py (sqlalchemy introspection) +OwlIdentity bar codes + PerFamilyCodebook + (optional) WoA's contracts/*.py DTOs + ↓ ↓ +UnifiedBridge::authorize(...) emits woa_ontology_lookup/_data.py + ↓ with module-level constants: +woa-rs sea-orm route handlers LOOKUP = { (table, col): OntologyMapping(...), ... } + TABLE_TO_OGIT_URI = { ... } + OGIT_URI_TO_FIBO = { ... } + CONTEXT_JSONLD = { ... } + ↓ + woa_ontology_lookup/__init__.py: + pure-Python O(1) dict lookups + no parse at request time + ↓ + WoA Python route handlers consume: + customer.ogit_uri() + customer.fibo_equivalent_of('firma') + customer.as_rdf(...) + /api/customer//rdf endpoint +``` + +The Python lookup module is **read-only data** generated from the same TTL files the Rust hydrators consume. Both languages get the same view. Updates flow through the source TTL → rerun build → ship a new lookup version → Python consumer upgrades. **The Python side never parses TTL at request time.** + +### 16.2 What "cheap" means concretely + +| Cost dimension | Target | +|---|---| +| Runtime CPU per lookup | O(1) dict access. No string parsing, no graph traversal, no Odoo XML-RPC, no SPARQL. | +| Runtime memory | ~1-5 MB resident for the full lookup dict (~9 entity tables × ~30 fields × ~10-field mapping shape). | +| Build-time CPU | ~5-30 s to extract once when TTL/alignment changes. Runs at `pip install` or `make ontology-lookup`. | +| Deps added to WoA Python | **One** package: `woa-ontology-lookup`. **ZERO** additional runtime deps. Optional: `rdflib` (~2 MB) only if §16.3 P4 federation surface ships. | +| Stefan's deployment impact | `pip install woa-ontology-lookup`. No new services, no new ports, no background workers. Flask restart. | +| Failure modes | Lookup miss returns `None` (silent passthrough). Build-time errors caught at extraction, never at runtime. | + +This matches the "cheap" the user named. WoA stays a Flask+SQLAlchemy app with one extra `pip install`. No architectural surgery. + +### 16.3 The four-phase Python rollout + +Each phase ships independently. Python phases gate on Rust-side D-ODOO-* deliverables landing but ship without coordinating release cycles. + +| Phase | Scope | Gate | Effort | Visible result | +|---|---|---|---|---| +| **P1 — bootstrap** | Create `woa-ontology-lookup` Python package: skeleton + extraction script (`build_lookup.py`) consuming OGIT WoA + DOLCE + PROV-O + QUDT TTL files (already shipped post-PR-407/408). Emit `_data.py` with module-level constants. Pure read-only API in `__init__.py`. Tests on the constant shape. | None — TTL already exists in `lance-graph/data/ontologies/`. | ~1 day | `from woa_ontology_lookup import lookup`; `lookup('customer', 'firma')` returns the OGIT URI for the Customer.firma field. | +| **P2 — Odoo integration** | Once D-ODOO-2 (Odoo `base` extraction) + D-ODOO-3 (alignment files) land in `lance-graph`, extend the build script to consume them. Adds `lookup('customer', 'firma').odoo_equivalent` etc. | D-ODOO-2 + D-ODOO-3 (Rust-side; ~1 weekend + 1 evening per §14.5). | ~1 day Python-side | `customer.odoo_equivalent_of('firma')` returns `'odoo:res.partner.name'`. Schema-parity check against Odoo's source-of-truth field names becomes a one-line lookup. | +| **P3 — runtime integration** | Add helper methods to WoA Python's models (`models.Customer.ogit_uri()`, `.fibo_equivalent_of(field)`, `.as_jsonld_context()`). Update REST API responses to include the JSON-LD `@context` derived from the lookup. Optional: structured-logging fields carry ontology URIs alongside table/column names. | P2 done. | ~1-2 days Python-side | API responses include `"@context": { "firma": "fibo:hasLegalName", ... }`. Linked-data tools can consume WoA's REST API natively. | +| **P4 — federation surface** | Add `/api///rdf` endpoints that return the record as RDF triples in the shared ontology (Turtle / JSON-LD / N-Triples via content negotiation). Uses `rdflib` to emit; the lookup module supplies the predicate URIs. Optional `/sparql` read-only endpoint for federated queries. | P3 done. | ~2 days Python-side | Federated tools (linked-data clients, Foundry-shape ETL, external XBRL/XRechnung validators) consume WoA records natively. | + +**Total Python-side effort: ~5-6 days for all four phases.** Stefan benefits from P3 (richer API responses) even before woa-rs PR-5 ships — i.e., the Python WoA gets schema-conscious before the Rust port does anything customer-visible. + + +### 16.4 The minimal package shape — concrete code skeleton + +```python +# woa_ontology_lookup/__init__.py +from ._data import LOOKUP, TABLE_TO_OGIT_URI, OGIT_URI_TO_FIBO, CONTEXT_JSONLD + +from dataclasses import dataclass +from typing import Optional + +@dataclass(frozen=True) +class OntologyMapping: + ogit_uri: str # canonical OGIT URI + fibo_equivalent: Optional[str] # fibo-be-le-lp:LegalEntity#hasLegalName if applicable + odoo_equivalent: Optional[str] # odoo:res.partner.name if applicable + vcard_equivalent: Optional[str] # vcard:Kind#fn if applicable + schemaorg_equivalent: Optional[str] + label_de: str # German UI label + label_en: str # English UI label + dolce_marker: str # 'Endurant' | 'Perdurant' | 'Quality' | 'Abstract' + dns_role: Optional[str] # DnS role classification + +def lookup(table: str, column: str) -> Optional[OntologyMapping]: + """O(1) dict access. Returns None if (table, column) is not mapped.""" + return LOOKUP.get((table.lower(), column.lower())) + +def ogit_uri_for_table(table: str) -> Optional[str]: + return TABLE_TO_OGIT_URI.get(table.lower()) + +def jsonld_context_for(table: str) -> dict: + """Returns the @context dict for a JSON-LD response for an entity of the given table.""" + return CONTEXT_JSONLD.get(table.lower(), {}) + +def as_rdf_triples(table: str, row: dict) -> list[tuple]: + """Maps a SQLAlchemy row dict to RDF triples. Optional rdflib export.""" + # Emit one triple per mapped column; uses OntologyMapping.ogit_uri as predicate. + ... + +# woa_ontology_lookup/_data.py (GENERATED — do not edit by hand) +LOOKUP = { + ('customer', 'firma'): OntologyMapping( + ogit_uri='ogit.WorkOrder:Customer.legalName', + fibo_equivalent='fibo-be-le-lp:LegalEntity#hasLegalName', + odoo_equivalent='odoo:res.partner.name', + vcard_equivalent='vcard:Kind#fn', + schemaorg_equivalent='schema:Organization#legalName', + label_de='Firma', + label_en='Company name', + dolce_marker='Endurant', + dns_role='LegalIdentity', + ), + ('customer', 'kdnr'): OntologyMapping(...), + ('workorder', 'betreff'): OntologyMapping(...), + # ... ~250 entries covering Customer + WorkOrder + Position + Tenant + + # User + Mahnung + Logbook + Dokument + Setting × ~30 fields each +} + +TABLE_TO_OGIT_URI = { + 'customer': 'ogit.WorkOrder:Customer', + 'workorder': 'ogit.WorkOrder:WorkOrder', + 'position': 'ogit.WorkOrder:Position', + 'mandant': 'ogit.WorkOrder:Tenant', + # ... +} + +CONTEXT_JSONLD = { ... } # per-table @context blob +``` + +### 16.5 Where this lives, who owns it + +| Repository | Role | What lives here | +|---|---|---| +| **`lance-graph`** (this repo) | source-of-truth TTL + alignment + extraction script | `data/ontologies/*.ttl` (incl. Odoo extraction output once D-ODOO-2 lands); a `python/woa_ontology_lookup/` sub-tree with `build_lookup.py` that converts TTL → `_data.py`. Build artifacts versioned + published to a Python index (PyPI or private) as `woa-ontology-lookup-.tar.gz`. | +| **`WoA`** (Python original, Stefan's source-of-truth app) | consumer | Single `requirements.txt` line: `woa-ontology-lookup~=1.0`. Use the package in models / routes / API responses. NO TTL files, NO build script — pure consumer. | +| **`woa-rs`** (Rust port) | consumer | Already gets the same data via `lance-graph-ontology::OntologyRegistry` per the POC plan. The Python lookup module and the Rust OntologyRegistry are **two language-native readers of one source of truth**. | + +This matches §4.6 read-only spine governance verbatim: writes flow through `MappingProposal::sha256()`-keyed dedup into the TTL files in `lance-graph`; both languages read; neither mutates. The Python package is the Python-language reader, analogous to the Rust `OntologyRegistry` being the Rust-language reader. + +### 16.6 What this UNLOCKS for WoA Python + +Concrete payoffs Stefan sees in WoA Python BEFORE the Rust port replaces anything: + +1. **Schema-conscious API responses.** WoA's REST routes return JSON-LD `@context` per response, so any linked-data client (or a Foundry-style ingest pipeline) reads WoA's API output as semantic data without a separate mapping step. ~1 day of Python work after P3. +2. **Odoo parity checks at build time.** When Odoo updates `l10n_de_skr04` and the Rust side picks up the change via D-ODOO-6, the Python lookup regenerates; any WoA field that drifts from the canonical Odoo shape is flagged at build time. CI gate. +3. **Federation via standard tools.** `/api/customer//rdf` returns Turtle / JSON-LD / N-Triples. Stefan's accountant runs an external XBRL validator or XRechnung tool against WoA output without manual format conversion. Removes a class of "but Odoo does X, WoA doesn't" friction. +4. **Drift detection before the Rust reconciler ships.** `python manage.py check-ontology-drift` spot-checks WoA's sqlalchemy schema against the OGIT WoA TTL. New columns missing from the TTL get flagged. Catches what `WoaMysqlReconciler` would catch at runtime — but earlier. +5. **Cognitive-model preparation.** Once the Rust side's cognitive substrate is wired (per §14.7 D-ODOO-10 end-state), Python WoA EXPORTS records via the federation surface (`/api///rdf`) and the Rust cognitive cascade reasons about them natively. This is the "Python ERP layer + Rust core" two-version bridge from `b9531cf3-odoo_work_steal_distillation.md` §"The two-version bridge" — Python doesn't change; Rust ingests via the federation surface. + +### 16.7 What this does NOT do + +- **Does NOT change WoA Python's data model.** The lookup module is a side-table — it says what URI corresponds to a field; it doesn't change what fields exist. Iron Rule №5 (don't break the source repo) preserved. +- **Does NOT add Odoo runtime dependencies.** No `odoo-bin`, no XML-RPC, no SQLAlchemy-on-Odoo's-PostgreSQL. Odoo information is EXTRACTED at build time and FROZEN into the Python module. Pure cache. +- **Does NOT introduce ontology reasoning into WoA Python.** No OWL reasoner, no SPARQL engine, no SHACL validator running in the Python process. Reasoning stays in the Rust cognitive substrate where it belongs. +- **Does NOT compete with woa-rs.** The Python lookup module and the Rust `OntologyRegistry` serve different processes; both read the same TTL. WoA Python keeps doing what it does; woa-rs ships the cognitive integration; the lookup module is shared schema awareness between them. + +### 16.8 Cross-references + +- §14.5 (D-ODOO-1..10) — the Rust-side Odoo extraction this Python phase consumes. +- `unified-bridge-consumer-migration-v1.md` §4.2 (Lance-cache persistence) — analogous read-only spine, Rust side. +- `unified-bridge-consumer-migration-v1.md` §4.6 (read-only spine + controlled write path) — governance applies to the Python lookup module verbatim: it reads, never mutates. +- `unified-bridge-consumer-migration-v1.md` §4.7 (per-OGIT storage) — Python lookup is organized by table-as-G-slot-analog; one dict subset per OGIT family. +- `b9531cf3-odoo_work_steal_distillation.md` §"The two-version bridge" — Python adapter substrate this proposal implements. +- `woa-rs/CLAUDE.md` Iron Rule №2 + №5 — the constraint envelope this proposal stays inside. + +### 16.9 One-line summary + +> WoA Python integrates Odoo via a build-time-extracted Python lookup module (`woa-ontology-lookup`) that mirrors the TTL files Rust's `lance-graph-ontology::OntologyRegistry` already consumes — same source, two language-native readers, no Odoo runtime dependency. ~5-6 days of Python work across 4 phases delivers: schema-conscious REST API responses (JSON-LD `@context`), build-time Odoo-parity drift checks, federation surface (`/api///rdf`) for external linked-data tools, and the substrate the Rust cognitive cascade ingests over for the two-version-bridge end-state. WoA's data model stays untouched (Iron Rule №5 preserved); Stefan's deployment gets one `pip install`. Python phases gate on Rust-side D-ODOO-2 + D-ODOO-3 landing but ship independently after. From 0cc6e81c21e1798532b887d1e1adbfd870a31ce1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 08:36:54 +0000 Subject: [PATCH 21/22] =?UTF-8?q?docs(plans/poc):=20=C2=A717=20=E2=80=94?= =?UTF-8?q?=20Stefan's=20conservative=20ZUGFeRD=20path=20(factur-x=20in=20?= =?UTF-8?q?Python=20today;=20backport=20to=20smb-office-rs=20x-rechnung-rs?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User scenario: Stefan is conservative, wants least-invasive route, needs ZUGFeRD/E-Rechnung as German law sales pitch, optionally backport synergies for smb-office-rs crates. Two findings drive the answer: 1. smb-office-rs/crates/x-rechnung-rs/ ALREADY EXISTS as a tenant-agnostic 73-line skeleton naming WoA (WT-30) + SAP (SIMAFPort-Rust) + SMB-Office C# (future FFI) as planned consumers. WT-30 sub-chunks (UBL/CII XML types + mapping + PDF/A-3 embed + CLI bin) are pending. 2. WoA Python uses fpdf2==2.8.3 for PDF today; NO ZUGFeRD/ factur-x library installed yet. So the answer is: Stefan ships ZUGFeRD via Python factur-x TODAY (conservative, days-out, no Rust dep), and his real- customer-validated WOA_TO_CII_MAPPING becomes the spec source for x-rechnung-rs WT-30 (multi-tenant from day 1). §17 NEW — covers: §17.1 Stefan's stack + what already ships in tree - WoA Python: Flask + SQLAlchemy + fpdf2; no factur-x - smb-office-rs: x-rechnung-rs skeleton + smb-woa (artikel+customer+auth) + customer-woa-bin - lance-graph: hydrate_zugferd + hydrate_zugferd_rules + SchematronHydrator + XsdHydrator (post-PR-407) - woa-rs: woa_pdf with GoBD Pflichtangaben reference (post PR #152) §17.2 Recommended path: factur-x Python library - Maintained by Alexis de Lattre (Akretion — same author as Odoo l10n_de_facturx); Odoo uses this library natively - Implements EN16931 + ZUGFeRD profiles (BASIC/EN16931/ EXTENDED/MINIMUM/factur-x 1.0.07) - Validates against EN16931 XSD + ZUGFeRD Schematron rules out of box — same surface lance-graph hydrators consume - Concrete code skeleton: WOA_TO_CII_MAPPING dict + build_cii_xml() + render_xrechnung() + 1 route handler; ~150 LOC total; existing WoaPDF stays unchanged §17.3 Sales pitch framing — German B-Rechnungspflicht timeline: - 2025-01-01: B2B must RECEIVE e-invoices - 2027-01-01: B2B > €800K turnover must ISSUE - 2028-01-01: ALL B2B must ISSUE Five pitch points: ahead of mandate by 2-3 years; zero customer manual work; EN16931-conformant validated at emit; drop-in for existing flow; free upgrade path to Rust later. §17.4 The high-leverage move: Stefan's WOA_TO_CII_MAPPING Python dict becomes the canonical spec for x-rechnung-rs WT-30 sub-chunk #2 (SemanticType → CII element mapping). Per-row real-customer validation > clean-room Rust authoring. 7-step backport workflow: TOML transcription → CII builder port → PDF/A-3 embed → XSD validation (binds to hydrate_zugferd) → Schematron (binds to hydrate_zugferd_ rules) → CLI bin → customer-woa-bin wiring. ~1-1.5 weeks Rust backport effort. §17.5 Bidirectional value loop diagram: same mapping table feeds Python (Stefan today) + Rust (x-rechnung-rs WT-30) + future consumers (SAP SIMAFPort-Rust, SMB-Office C# via FFI, direct Rust lib imports). Convergence point. §17.6 What NOT to do: don't reimplement EN16931 from XSD; don't adopt Mustang (Java/JVM dep); don't wait for woa-rs PR-5; don't build Rust before Python validation baseline; don't change WoA data model (Iron Rule №5); don't add direct lance-graph dep to Python (Iron Rule №2). §17.7 7-week sequencing: Stefan ships Week 1; Sonnet subagent transcribes mapping Week 2; x-rechnung-rs WT-30 sub-chunks land Weeks 3-6; optional Python→Rust swap via CLI subprocess Week 7+. Stefan never blocks on Rust; Rust never blocks on Stefan. §17.8 One-line summary: pip install factur-x + 1 Python file + 1 route + 1 mapping table = sales-pitch-ready ZUGFeRD in ~1 week; mapping backports to x-rechnung-rs as canonical spec source; byte-identical EN16931 output across both runtimes; consumers pick impl that fits their deployment. The convergence is structural: x-rechnung-rs already plans WoA as first tenant; Stefan's real-customer validation becomes the spec source the Rust port references. Both ship value; neither blocks the other. --- ...-graph-business-logic-poc-via-woa-rs-v1.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md index 236b6153..5c3bf168 100644 --- a/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md +++ b/.claude/plans/lance-graph-business-logic-poc-via-woa-rs-v1.md @@ -687,3 +687,205 @@ Concrete payoffs Stefan sees in WoA Python BEFORE the Rust port replaces anythin ### 16.9 One-line summary > WoA Python integrates Odoo via a build-time-extracted Python lookup module (`woa-ontology-lookup`) that mirrors the TTL files Rust's `lance-graph-ontology::OntologyRegistry` already consumes — same source, two language-native readers, no Odoo runtime dependency. ~5-6 days of Python work across 4 phases delivers: schema-conscious REST API responses (JSON-LD `@context`), build-time Odoo-parity drift checks, federation surface (`/api///rdf`) for external linked-data tools, and the substrate the Rust cognitive cascade ingests over for the two-version-bridge end-state. WoA's data model stays untouched (Iron Rule №5 preserved); Stefan's deployment gets one `pip install`. Python phases gate on Rust-side D-ODOO-2 + D-ODOO-3 landing but ship independently after. + +--- + +## 17 — Stefan's conservative ZUGFeRD/E-Rechnung path (least-invasive Python; backport to smb-office-rs x-rechnung-rs) (2026-05-21, same session) + +User scenario: Stefan is conservative, wants the least-invasive route, needs ZUGFeRD/E-Rechnung as per German law as a sales pitch, and would optionally like to backport synergies for the existing crates in smb-office-rs (X-Rechnung, ZUGFeRD, ELSTER, "whatever is there"). + +### 17.1 Stefan's constraints + what already exists in tree + +Stefan's WoA Python stack today: +- Web framework: Flask +- DB: MySQL via SQLAlchemy +- PDF: **`fpdf2==2.8.3`** (subclassed as `WoaPDF(FPDF)` in `pdf_gen.py`) +- Optional: `weasyprint` for HTML→PDF (best-effort) +- No ZUGFeRD / factur-x / xrechnung Python library installed yet + +What ships in `smb-office-rs` today (already in tree, complementary to Stefan): +- **`crates/x-rechnung-rs/`** (73-line skeleton lib.rs, 65-line Cargo.toml) — explicitly **tenant-agnostic**; doc-comments name three planned consumers: **WoA (WT-30)** = Stefan's invoices to public-sector customers; **SIMAFPort-Rust** (future) = SAP-side BelegKopf → X-Rechnung; **SMB-Office** (future) = German Steuerberater invoices via FFI. Status: skeleton; subsequent WT-30 sub-chunks land UBL 2.1 / CII XML + PDF/A-3 hybrid + CLI bin. +- **`crates/smb-woa/`** — contains `artikel.rs` + `customer.rs` + `auth/` modules. Stefan's WoA-vertical scaffolding inside the smb-office-rs workspace. +- **`crates/customer-woa-bin/`** — per-customer binary targeting Stefan's deployment (per smb-office-rs's "single binary per customer" iron rule). + +What ships in `lance-graph` (already in tree post-PR-407/408): +- **`hydrate_zugferd` + `hydrate_zugferd_rules`** — XSD + Schematron hydration of EN16931 invoice schema + ZUGFeRD/Factur-X compliance rules. The CII element shapes Stefan's invoices will map to. +- **`SchematronHydrator` + `XsdHydrator`** — the substrate `hydrate_zugferd_rules` builds on. + +What ships in `woa-rs` (already in tree per the recent PRs #150 / #151 / #152): +- **`crates/woa_pdf/`** with invoice-wording patterns rewritten against Python source + GoBD Pflichtangaben reference (`d74a7e7` + `5af2ca5` from the PR #152 merge). This is the future home of Rust-side PDF generation. + +The synergy that's structurally already-baked: **x-rechnung-rs explicitly names WoA as first tenant; smb-woa already mirrors Stefan's vertical inside the smb-office-rs workspace; woa-rs's woa_pdf carries the GoBD/Pflichtangaben pattern reference; lance-graph ships the EN16931 substrate.** The pieces are aligned; Stefan just needs to ship the Python-side artefact. + +### 17.2 The least-invasive Python path — `factur-x` library + ~50-LOC route + +**Recommended: Stefan installs `factur-x` Python library** (https://pypi.org/project/factur-x/) — purpose-built for Factur-X / ZUGFeRD generation. Pure Python; one `pip install`; runtime deps are `lxml` (already common) + `reportlab` (PDF/A-3 embedding) + `PyPDF2`/`pypdf`. + +Why this library specifically: +- Maintained by Alexis de Lattre (Akretion) — **same author as the Odoo factur-x integration module** (`l10n_de_facturx`). What Odoo uses for its e-invoice support IS this library. Cross-pollination is built in. +- Implements EN16931 + ZUGFeRD profiles BASIC / EN 16931 / EXTENDED / MINIMUM + factur-x-1.0.07 (current at writing). +- Validates against the EN16931 XSD + ZUGFeRD Schematron rules out of the box — same rules `hydrate_zugferd_rules` parses on the Rust side; you get the same validation surface either way. +- Generates the embedded XML AND embeds it into a PDF/A-3 in one call (`facturx.generate_facturx_from_binary(pdf_bytes, xml_bytes, ...)`). +- Pure additive in WoA Python — Stefan's existing `WoaPDF(FPDF)` generator stays; factur-x reads its output and adds the XML + PDF/A-3 metadata layer. + +Concrete integration shape: + +```python +# WoA Python — new file: woa/erechnung.py (~50-80 LOC) +from facturx import generate_from_file, generate_facturx_from_binary +from lxml import etree +from io import BytesIO + +from woa.models import Vorgang, Customer +from woa.pdf_gen import WoaPDF # existing fpdf2 subclass + +# Mapping table: WoA Vorgang field → EN16931 CII element XPath. +# This IS the spec artefact that backports into smb-office-rs/crates/x-rechnung-rs +# WT-30 sub-chunk "SemanticType → CII element mapping (ram:*, cbc:*, cac:*)". +# Single source of truth; one place to maintain when EN16931 evolves. +WOA_TO_CII_MAPPING = { + "vorgang.beleg_nr": "rsm:ExchangedDocument/ram:ID", + "vorgang.datum": "rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString", + "vorgang.brutto_summe": "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount", + "vorgang.netto_summe": "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:LineTotalAmount", + "vorgang.mwst_summe": "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:ApplicableTradeTax/ram:CalculatedAmount", + "customer.firma": "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty/ram:Name", + "customer.steuernummer": "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty/ram:SpecifiedTaxRegistration/ram:ID", + "customer.ustid": "rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeAgreement/ram:BuyerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID='VA']", + "position.bezeichnung": "rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedTradeProduct/ram:Name", + "position.menge": "rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity", + "position.einzelpreis": "rsm:SupplyChainTradeTransaction/ram:IncludedSupplyChainTradeLineItem/ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount", + # ... ~30-50 more entries covering all Pflichtangaben per § 14 UStG + + # GoBD invariants per the woa_pdf reference Stefan already has. +} + +def build_cii_xml(vorgang: Vorgang, customer: Customer) -> bytes: + """Build EN16931 CII XML from WoA models. Uses lxml for XPath-based assignment.""" + # Start from a templated EN16931 skeleton (ships with factur-x as `cii.xml.j2`) + # or built fresh with lxml. ~80 LOC of XPath assignment using WOA_TO_CII_MAPPING. + ... + +def render_xrechnung(vorgang_id: int) -> bytes: + """The one new route handler — emits a hybrid PDF/A-3 with EN16931 XML embedded.""" + vorgang = Vorgang.query.get_or_404(vorgang_id) + customer = vorgang.customer + # 1. Render PDF as today via existing WoaPDF(FPDF) subclass: + pdf_bytes = WoaPDF.build(vorgang=vorgang, customer=customer).output(dest='S') + # 2. Build EN16931 CII XML: + xml_bytes = build_cii_xml(vorgang, customer) + # 3. Embed XML into PDF/A-3 via factur-x — output is fully compliant Factur-X: + facturx_pdf = generate_facturx_from_binary( + pdf_bytes, + xml_bytes, + check_xsd=True, # validate against EN16931 XSD at emit time + afrelationship='Data', # PDF/A-3 attachment relationship marker + ) + return facturx_pdf + +# In Stefan's existing blueprint registration: +@bp.route('/vorgaenge//rechnung/xrechnung', methods=['GET']) +def xrechnung(wid): + pdf = render_xrechnung(wid) + return Response(pdf, mimetype='application/pdf', + headers={'Content-Disposition': f'attachment; filename=Rechnung_{wid}.pdf'}) +``` + +**Total Stefan-side surgery**: 1 new file (`woa/erechnung.py`, ~150 LOC); 1 line in his blueprint route registration; 1 line in `requirements.txt` (`factur-x>=3.0`). **No data model changes; no schema migration; no new services; no JVM; no Rust.** Stefan's existing PDF generator stays unchanged — factur-x reads its output and ADDS the XML + PDF/A-3 metadata layer. + +### 17.3 The sales pitch framing — German B-Rechnungspflicht 2025-2028 + +The German E-Rechnungspflicht timeline (per Wachstumschancengesetz 2024 + Bundesfinanzministerium guidance): + +| Effective date | Obligation | +|---|---| +| 2025-01-01 | All German B2B businesses must be ABLE TO RECEIVE e-invoices (XRechnung / ZUGFeRD EN16931 conformant). Email + PDF stays acceptable to RECEIVE if recipient consents. | +| 2027-01-01 | B2B businesses with > €800K annual turnover must ISSUE e-invoices for domestic B2B transactions. Paper / non-conformant PDF prohibited. | +| 2028-01-01 | All German B2B businesses, regardless of turnover, must ISSUE e-invoices for domestic B2B. | + +What Stefan can pitch with the §17.2 implementation **TODAY**: + +- **"We're ahead of the 2027/2028 mandate by 2-3 years."** Stefan's customers (mid-market German businesses) face the same mandate. Showing them WoA already emits conformant XRechnung is differentiator. +- **"Zero manual work for your customers."** The hybrid PDF/A-3 is a normal PDF that opens in any viewer; the embedded XML is what their accounting software / DATEV import / ELSTER UStVA picks up automatically. +- **"EN16931-conformant validated at emit time."** `factur-x`'s `check_xsd=True` runs the same validation the official `xeinkauf.de` validator runs — Stefan's invoices are verifiably correct, not just "we tried." +- **"Drop-in for your existing workflow."** Stefan's existing customer-facing flow (print + email) doesn't change. The same PDF Stefan already sends is now also a valid e-invoice; recipients who care about the XML extract it; others see the PDF as before. +- **"Free upgrade path to Rust performance later."** When `x-rechnung-rs` WT-30 ships (per smb-office-rs), Stefan's Python integration backports cleanly because the WOA_TO_CII_MAPPING table is the shared spec — switch the impl, keep the table. + +For Stefan's specific customer base (small / mid-market German IT services per the WoA glossary), this is the kind of compliance story that converts. The cost is one feature-week of Python work; the customer-facing differentiator runs for 3+ years. + +### 17.4 Backport opportunity — Stefan's Python integration AS x-rechnung-rs WT-30 spec source + +The high-leverage move: **Stefan's `WOA_TO_CII_MAPPING` Python dict (~30-50 mapping rows) becomes the canonical spec for `smb-office-rs/crates/x-rechnung-rs/` WT-30's `SemanticType → CII element mapping (ram:*, cbc:*, cac:*)` sub-chunk** (which the existing `x-rechnung-rs/src/lib.rs:48-52` doc-comment names as sub-chunk #2). + +Why this is the right artefact to backport: + +1. **Stefan's mapping is real-customer validated.** Stefan invoices real customers; if the EN16931 XML doesn't validate against `xeinkauf.de`'s validator, his customers' accountants notice. The mapping passes the empirical test that any clean-room Rust spec authoring would have to repeat. +2. **The mapping is language-agnostic.** It's `(WoA field name, CII XPath)` pairs. Python dict, YAML, JSON, Rust `phf::Map` — same data, different reader. +3. **x-rechnung-rs explicitly named WoA as first tenant.** Per `x-rechnung-rs/src/lib.rs:19` doc-comment: "WoA (WT-30) — small-business invoices to public-sector customers." Stefan's work IS the WT-30 ship vehicle. +4. **smb-woa already exists as the WoA-vertical landing zone.** `crates/smb-woa/` has `artikel.rs` + `customer.rs` + `auth/` — Stefan's vertical's Rust types live here. The mapping table goes in `crates/smb-woa/src/erechnung_mapping.rs` (or `crates/x-rechnung-rs/src/woa.rs` if x-rechnung-rs prefers consumer modules co-located). + +Concrete backport workflow: + +| # | Source artefact | Target landing | Action | +|---|---|---|---| +| 1 | `WOA_TO_CII_MAPPING` Python dict (from §17.2) | `crates/x-rechnung-rs/data/woa-mapping.toml` | Stefan / WoA Python ships the dict. Sonnet subagent transcribes to TOML. ~1 hour. | +| 2 | `build_cii_xml(vorgang, customer)` Python function | `crates/x-rechnung-rs/src/cii_builder.rs` | Per-mapping-row codegen: each row gets a typed Rust struct field with a `to_cii_xpath()` method. Hand-port + tests. ~2 days. | +| 3 | `generate_facturx_from_binary` Python call | `crates/x-rechnung-rs/src/pdf_a3_embed.rs` | Use `lopdf` or `pdf-rs` Rust crate for the PDF/A-3 embedding. ~2 days. The Python lib's algorithm (PDF/A-3 metadata + `/AFRelationship Data` marker + `/EmbeddedFiles` name tree) is documented in the factur-x source; transcribable. | +| 4 | EN16931 XSD validation (via factur-x's `check_xsd=True`) | `crates/x-rechnung-rs/src/validate.rs` | Already partly covered by `hydrate_zugferd` in lance-graph-ontology (XSD hydration). x-rechnung-rs binds to it via lance-graph-contract::ontology::SemanticType. ~1 day. | +| 5 | ZUGFeRD Schematron compliance rules | `crates/x-rechnung-rs/src/schematron.rs` | Already partly covered by `hydrate_zugferd_rules` + `SchematronHydrator`. Wire to consume. ~1 day. | +| 6 | CLI binary | `crates/x-rechnung-rs/bin/x-rechnung.rs` | Per the doc-comment "subsequent WT-30 sub-chunk #4 — CLI binary (`x-rechnung` bin under `cli` feature)". ~1 day. | +| 7 | Per-tenant WoA-mapping consumption | `crates/customer-woa-bin/src/main.rs` | Wires `smb-woa::Vorgang` → `x-rechnung-rs::generate_xrechnung(vorgang)` for Stefan's deployment. ~0.5 day. | + +**Total Rust backport effort**: ~1-1.5 weeks. The Python ships in days; the Rust ports the spec over weeks. Stefan never blocks on the Rust side because his Python implementation runs from day 1. + +### 17.5 Bidirectional value loop + +Two value streams flow through the same artefact: + +``` +Stefan ships factur-x integration x-rechnung-rs WT-30 ports it to Rust +in WoA Python (1 week) (1-1.5 weeks; multi-tenant from day 1) + │ │ + │ produces WOA_TO_CII_MAPPING │ accepts the same mapping + │ as the EN16931 reference spec │ as its sub-chunk #2 spec + │ │ + └─────────────────────┬────────────────────────┘ + ▼ + SAME EN16931-conformant XML output + (byte-identical at the wire layer) + │ + ▼ + ┌──────────────────────────────┐ + │ Stefan's customers (today) │ ◄── WoA Python emits via factur-x + │ SAP installations (future) │ ◄── SIMAFPort-Rust emits via x-rechnung-rs + │ SMB-Office C# (future) │ ◄── via FFI to x-rechnung-rs + │ Other Rust consumers (future)│ ◄── direct lib import of x-rechnung-rs + └──────────────────────────────┘ +``` + +The mapping table is the convergence point. Once Stefan validates a row against real customer feedback (e.g., "the buyer's UStID needs `schemeID='VA'` per German Reverse-Charge §13b case"), the fix lands in the shared mapping; every consumer benefits. + +### 17.6 What NOT to do + +| Anti-pattern | Why not | +|---|---| +| Stefan reimplements EN16931 from XSD in pure-Python | Years of work; the EN16931 + ZUGFeRD Schematron + PDF/A-3 spec corpus is ~500 pages. `factur-x` Python lib is 5+ years mature; uses it. | +| Stefan adopts Mustang (Java) via subprocess | Adds JVM runtime dep — Stefan's Railway deployment doesn't ship Java; ops complexity. Mustang's behavioural-parity advantage doesn't outweigh the deployment friction. | +| Stefan waits for woa-rs PR-5 (the Rust XRechnung milestone) | Months-out vs days-out. Stefan ships TODAY with Python; the woa-rs path is for the engineering completeness POC (§2 of this plan), not the sales pitch. | +| Stefan builds x-rechnung-rs WT-30 himself (Rust) before having a real Python validation baseline | Risk: ships Rust impl that fails real-customer validation. Better path: Python first, harvest spec from real-customer wins, Rust second. | +| Stefan changes WoA Python's data model to absorb EN16931 field shapes | Iron Rule №5 — don't break the source repo. WOA_TO_CII_MAPPING is a side-table mapping existing fields; no schema change. | +| Stefan adds direct lance-graph dependency to WoA Python | WoA Python stays canonical (Iron Rule №2). lance-graph integration is the Rust port's concern. Python-side just consumes factur-x + maintains the mapping. | + +### 17.7 Sequencing + +| Week | Stefan (WoA Python) | smb-office-rs (x-rechnung-rs) | woa-rs (woa_pdf) | +|---|---|---|---| +| **1** (Nov 2025?) | Install factur-x; write `woa/erechnung.py` with WOA_TO_CII_MAPPING; new route; smoke test against `xeinkauf.de` validator. **Ship to first customer.** | Track Stefan's mapping work. No code yet. | No interaction; PR #152 already landed the GoBD Pflichtangaben reference. | +| **2** | Iterate on customer feedback (e.g., UStID `schemeID='VA'` corrections). | Sonnet subagent transcribes Stefan's Python dict to `data/woa-mapping.toml`. | — | +| **3-4** | Marketing rollout — sales-pitch material around "EN16931-conformant, ahead of 2027 mandate." | WT-30 sub-chunks #1 (CII XML types) + #2 (mapping consumption from TOML) land. | — | +| **5-6** | Stay on Python; absorb any further customer feedback into the mapping. | WT-30 sub-chunk #3 (PDF/A-3 embed) + #4 (CLI bin) land. `cargo run -p x-rechnung -- --woa-mapping woa-mapping.toml vorgang.json` produces byte-identical output to Stefan's Python. | woa_pdf consumes x-rechnung-rs for its own PDF generation path (when woa-rs PR-5 ships per POC §2). | +| **7+** | Optional: Stefan swaps factur-x Python for a `subprocess` call to `x-rechnung` CLI for performance / consistency. Same mapping, same output, different runtime. Iron Rule №5 still preserved (WoA data model unchanged). | x-rechnung-rs ships v1.0; multi-tenant story (WoA + future SAP + future SMB-Office) operational. | — | + +### 17.8 One-line summary + +> Stefan's conservative path: `pip install factur-x` + 1 new Python file (`woa/erechnung.py`, ~150 LOC) + 1 new route (`POST /vorgaenge//rechnung/xrechnung`) + 1 mapping table (`WOA_TO_CII_MAPPING` = ~30-50 `(WoA_field, CII_XPath)` rows). Ships in 1 week, no Rust dep, no JVM, no data model change (Iron Rule №5 preserved). Sales pitch: "ahead of the 2027/2028 German B-Rechnungspflicht mandate." The mapping table backports into `smb-office-rs/crates/x-rechnung-rs/` WT-30 as the canonical SemanticType → CII element spec — Stefan's real-customer-validated wire shape becomes the reference implementation for the multi-tenant Rust port (WoA + SAP + SMB-Office). Byte-identical EN16931 output across both runtimes; consumers pick the impl that fits their deployment (Python for Stefan today, Rust CLI/FFI for SAP/SMB-Office tomorrow). + From ecb92c012b37e66f6d53139f51f94d5ccc707692 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 26 May 2026 15:58:40 +0000 Subject: [PATCH 22/22] =?UTF-8?q?feat(callcenter):=20build=20odoo=5Falignm?= =?UTF-8?q?ent=20cache=20(OGIT=E2=86=92OWL=E2=86=92odoo)=20the=20harvest?= =?UTF-8?q?=20BRIEFING=20assumes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The odoo-richness harvest BRIEFING (woa-rs/.claude/odoo/) documents an "already built" odoo_alignment.rs with resolve_odoo / resolve_odoo_to_family / dolce_odoo — but it never existed in either repo. This builds it for real as the single-source spine module (consumers depend on it; no skr_data mirror, per the per-OGIT-storage / read-only-spine invariant). Two O(1) legs: odoo class --owl:equivalentClass--> OWL pivot --> OGIT family+slot --> FamilyEntry - resolve_odoo(class) -> Option: leg 1 (static seed). - resolve_odoo_to_family(class, &table): both legs, confirmed live against the family's hydrated OgitFamilyTable (wrong-family-safe, no panic). - resolve_odoo_entry(class, &table) -> Option<&FamilyEntry>: lands on the inline entry the lanes read for inherited thinking style. - dolce_odoo(class): DOLCE marker from suffix rules. - seed_family_table(&mut table): populate foundry slots (hydration overlay/tests). Option B (no minted family/slot): the four foundry family bytes are restated from data/family_registry.ttl (BillingCore 0x61, SMBAccounting 0x62, SmbFoundryCustomer 0x80, SmbFoundryInvoice 0x81) and asserted against it in a test. Seed rows: res.partner->fibo:LegalEntity, account.move->fibo:Transaction, account.move.line->fibo:JournalEntryLine, account.account->fibo:Account, account.account.template(SKR)->fibo:Account, product.*->schema:Product. Unmapped classes (stock.move, sale.order, hr.*, account.reconcile.model) resolve to None — the Layer-2-axiom signal, not an invented family. 9 unit tests; cargo check + clippy + fmt clean. --- crates/lance-graph-callcenter/src/lib.rs | 12 + .../src/odoo_alignment.rs | 420 ++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 crates/lance-graph-callcenter/src/odoo_alignment.rs diff --git a/crates/lance-graph-callcenter/src/lib.rs b/crates/lance-graph-callcenter/src/lib.rs index 2452b83d..903c42d5 100644 --- a/crates/lance-graph-callcenter/src/lib.rs +++ b/crates/lance-graph-callcenter/src/lib.rs @@ -174,6 +174,18 @@ pub use family_table::{ FamilyEntry, OgitFamilyTable, OwlCharacteristics, PerFamilyCodebook, SchemaKind, }; +// Odoo → OWL → OGIT alignment cache (the "two-version bridge" leg). Static +// seed binding odoo models (res.partner, account.move(.line), account.account, +// product.*, SKR) to their owl:equivalentClass pivots and the foundry family +// + slot they inherit under Option B. Single source — woa-rs skr_data consumes +// via this dep, never mirrors. See module doc + woa-rs/.claude/odoo/BRIEFING.md. +pub mod odoo_alignment; +pub use odoo_alignment::{ + dolce_odoo, resolve_odoo, resolve_odoo_entry, resolve_odoo_to_family, seed_family_table, + OwlPivot, FAMILY_BILLING_CORE, FAMILY_SMB_ACCOUNTING, FAMILY_SMB_FOUNDRY_CUSTOMER, + FAMILY_SMB_FOUNDRY_INVOICE, +}; + // PR-F1 — UnifiedBridgeGate: production CognitiveBridgeGate impl. // Wraps UnifiedBridge; Chinese-wall check fires before policy evaluation // on cross-tenant ops (§3.8). No dep on thinking-engine from thinking-engine. diff --git a/crates/lance-graph-callcenter/src/odoo_alignment.rs b/crates/lance-graph-callcenter/src/odoo_alignment.rs new file mode 100644 index 00000000..565ffef2 --- /dev/null +++ b/crates/lance-graph-callcenter/src/odoo_alignment.rs @@ -0,0 +1,420 @@ +//! Odoo → OWL → OGIT alignment cache (the "two-version bridge" leg). +//! +//! This is the static seed the odoo-richness harvest lanes +//! (`woa-rs/.claude/odoo/`) and the eventual woa-rs `skr_data` consumer read +//! to attach an OGIT identity — and therefore an inherited *thinking style* — +//! to every odoo concept they steal. It is the **single source**: consumers +//! depend on `lance-graph-callcenter` and call these functions; they MUST NOT +//! mirror the table (the spine goes dirty the moment two copies drift — see +//! the per-OGIT-storage invariant in `super-domain-rbac-tenancy-v1`). +//! +//! ## The chain (two legs, both O(1)) +//! +//! ```text +//! odoo class ──owl:equivalentClass──► OWL pivot ──► OGIT family + slot ──► FamilyEntry +//! resolve_odoo() (fibo/schema) (Option B: inherit) OgitFamilyTable.lookup() +//! └──────────────── resolve_odoo_to_family(class, &table) ───────────────┘ +//! └──────────────── resolve_odoo_entry(class, &table) ───────► &FamilyEntry +//! ``` +//! +//! **Option B (locked):** no new CAM family, no freshly-minted slot per odoo +//! class. Each class *inherits* an existing foundry family + slot via its OWL +//! pivot. The four foundry families are defined authoritatively in +//! `data/family_registry.ttl` (BillingCore 0x61, SMBAccounting 0x62, +//! SmbFoundryCustomer 0x80, SmbFoundryInvoice 0x81); the constants below +//! restate those bytes so the alignment binds to the same basins hydration +//! loads. Classes with no existing family (`stock.move`, `sale.order`, +//! `hr.*`, `account.reconcile.model`, …) resolve to `None` — that is the +//! signal to author a Layer-2 alignment axiom, not to invent a family. + +use crate::family_table::{FamilyEntry, OgitFamilyTable, OwlCharacteristics, SchemaKind}; +use crate::super_domain::DolceMarker; +use crate::unified_bridge::{OgitFamily, OwlIdentity}; + +// ═══════════════════════════════════════════════════════════════════════════ +// Foundry family bytes — restated from data/family_registry.ttl (Option B) +// ═══════════════════════════════════════════════════════════════════════════ + +/// `ogit:BillingCore` — billable items / billing surface. familyId 97. +pub const FAMILY_BILLING_CORE: OgitFamily = OgitFamily(0x61); +/// `ogit:SMBAccounting` — double-entry substrate (accounts, posting lines). +/// familyId 98. +pub const FAMILY_SMB_ACCOUNTING: OgitFamily = OgitFamily(0x62); +/// `ogit:SmbFoundryCustomer` — partner / legal-entity master data. familyId 128. +pub const FAMILY_SMB_FOUNDRY_CUSTOMER: OgitFamily = OgitFamily(0x80); +/// `ogit:SmbFoundryInvoice` — invoice / transaction document. familyId 129. +pub const FAMILY_SMB_FOUNDRY_INVOICE: OgitFamily = OgitFamily(0x81); + +// ═══════════════════════════════════════════════════════════════════════════ +// OwlPivot — the resolved owl:equivalentClass landing (leg 1 output) +// ═══════════════════════════════════════════════════════════════════════════ + +/// What an odoo model resolves to: the OWL pivot URI plus the OGIT address +/// (family + slot) it inherits under Option B, plus its DOLCE marker. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OwlPivot { + /// `owl:equivalentClass` target, e.g. `"fibo:LegalEntity"`, + /// `"fibo:Transaction"`, `"schema:Product"`. + pub pivot_uri: &'static str, + /// Inherited foundry basin. + pub family: OgitFamily, + /// Inherited within-family slot. + pub slot: u16, + /// DOLCE upper marker (Endurant / Perdurant / Quality / Abstract). + pub dolce: DolceMarker, +} + +impl OwlPivot { + /// The 3-byte OGIT row identity this pivot inherits. + #[inline] + pub const fn identity(self) -> OwlIdentity { + OwlIdentity::new(self.family, self.slot) + } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// dolce_odoo — DOLCE marker from odoo class suffix rules +// ═══════════════════════════════════════════════════════════════════════════ + +/// Classify an odoo class onto its DOLCE upper marker from structural suffix +/// rules. Independent of the seed table so unmapped-but-recognisable classes +/// (e.g. `sale.order`) still get a marker the harvest lanes can record. +/// +/// - **Perdurant** — transactional events / processes: `*.move`, +/// `*.move.line`, `*.payment`, `*.order`, `*.order.line`, bank statements, +/// stock moves, pickings. +/// - **Abstract** — rules / classifications / models: `*.tax`, +/// `*.fiscal.position`, `*.reconcile.model`, `*.payment.term`. +/// - **Endurant** — persistent master-data objects: `res.*`, `*.account`, +/// `product.*`. +/// - **Unknown** — no rule matched. +pub fn dolce_odoo(class: &str) -> DolceMarker { + if class.ends_with(".move") + || class.ends_with(".move.line") + || class.ends_with(".payment") + || class.ends_with(".order") + || class.ends_with(".order.line") + || class.ends_with("bank.statement") + || class.ends_with(".picking") + || class == "stock.move" + { + return DolceMarker::Perdurant; + } + if class.ends_with(".tax") + || class.ends_with("fiscal.position") + || class.ends_with("reconcile.model") + || class.ends_with("payment.term") + { + return DolceMarker::Abstract; + } + if class.starts_with("res.") || class.ends_with(".account") || class.starts_with("product.") { + return DolceMarker::Endurant; + } + DolceMarker::Unknown +} + +// ═══════════════════════════════════════════════════════════════════════════ +// Seed — the realized rows the BRIEFING enumerates (res.partner, account.*, +// product.*, SKR). Each row owns a stable slot within its foundry family. +// ═══════════════════════════════════════════════════════════════════════════ + +struct OdooSeedRow { + /// odoo model name (the resolvable key). + odoo_class: &'static str, + /// `owl:equivalentClass` pivot URI. + pivot_uri: &'static str, + family: OgitFamily, + slot: u16, + kind: SchemaKind, + dolce: DolceMarker, + /// Canonical OGIT label this slot carries inside the foundry family table. + label_uri: &'static str, + /// `dcterms:source` lineage stamped into the `FamilyEntry`. + provenance: &'static str, +} + +impl OdooSeedRow { + /// Build the inline `FamilyEntry` this row populates in its family table. + fn entry(&self) -> FamilyEntry { + FamilyEntry { + label_uri: self.label_uri, + kind: self.kind, + owl_characteristics: OwlCharacteristics::EMPTY, + dolce_marker: self.dolce, + axiom_blob: &[], + provenance: self.provenance, + verbs: &[], + } + } +} + +/// The realized alignment rows. Small fixed table — `resolve_odoo`'s linear +/// scan over a handful of entries is effectively O(1). +static ODOO_SEED: &[OdooSeedRow] = &[ + OdooSeedRow { + odoo_class: "res.partner", + pivot_uri: "fibo:LegalEntity", + family: FAMILY_SMB_FOUNDRY_CUSTOMER, + slot: 1, + kind: SchemaKind::Entity, + dolce: DolceMarker::Endurant, + label_uri: "ogit.SMB:Customer", + provenance: "odoo res.partner (company facet) =owl:equivalentClass=> fibo:LegalEntity", + }, + OdooSeedRow { + odoo_class: "account.move", + pivot_uri: "fibo:Transaction", + family: FAMILY_SMB_FOUNDRY_INVOICE, + slot: 1, + kind: SchemaKind::Entity, + dolce: DolceMarker::Perdurant, + label_uri: "ogit.SMB:Invoice", + provenance: "odoo account.move =owl:equivalentClass=> fibo:Transaction", + }, + OdooSeedRow { + odoo_class: "account.move.line", + pivot_uri: "fibo:JournalEntryLine", + family: FAMILY_SMB_ACCOUNTING, + slot: 1, + kind: SchemaKind::Entity, + dolce: DolceMarker::Perdurant, + label_uri: "ogit.SMBAccounting:JournalEntryLine", + provenance: "odoo account.move.line =owl:equivalentClass=> fibo:JournalEntryLine", + }, + OdooSeedRow { + odoo_class: "account.account", + pivot_uri: "fibo:Account", + family: FAMILY_SMB_ACCOUNTING, + slot: 2, + kind: SchemaKind::Entity, + dolce: DolceMarker::Endurant, + label_uri: "ogit.SMBAccounting:Account", + provenance: "odoo account.account =owl:equivalentClass=> fibo:Account", + }, + OdooSeedRow { + odoo_class: "account.account.template", + pivot_uri: "fibo:Account", + family: FAMILY_SMB_ACCOUNTING, + slot: 3, + kind: SchemaKind::Entity, + dolce: DolceMarker::Endurant, + label_uri: "ogit.SMBAccounting:SkrAccount", + provenance: "SKR03/04 chart concept (odoo account.account.template) => fibo:Account", + }, + OdooSeedRow { + odoo_class: "product.template", + pivot_uri: "schema:Product", + family: FAMILY_BILLING_CORE, + slot: 1, + kind: SchemaKind::Entity, + dolce: DolceMarker::Endurant, + label_uri: "ogit.Billing:Product", + provenance: "odoo product.template =owl:equivalentClass=> schema:Product", + }, + OdooSeedRow { + odoo_class: "product.product", + pivot_uri: "schema:Product", + family: FAMILY_BILLING_CORE, + slot: 2, + kind: SchemaKind::Entity, + dolce: DolceMarker::Endurant, + label_uri: "ogit.Billing:ProductVariant", + provenance: "odoo product.product (variant) =owl:equivalentClass=> schema:Product", + }, +]; + +// ═══════════════════════════════════════════════════════════════════════════ +// Resolution surface — the three BRIEFING-named functions + entry lookup +// ═══════════════════════════════════════════════════════════════════════════ + +/// Leg 1: resolve an odoo class to its OWL pivot + inherited OGIT address. +/// +/// Exact seed match first; then a `product.*` prefix fallback onto the +/// generic `product.template` row (schema:Product / BillingCore). Returns +/// `None` for any class with no existing family (the Layer-2-axiom signal). +pub fn resolve_odoo(class: &str) -> Option { + if let Some(row) = ODOO_SEED.iter().find(|r| r.odoo_class == class) { + return Some(OwlPivot { + pivot_uri: row.pivot_uri, + family: row.family, + slot: row.slot, + dolce: row.dolce, + }); + } + // Unseen product subtype → inherit the generic product slot. + if class.starts_with("product.") { + let row = ODOO_SEED + .iter() + .find(|r| r.odoo_class == "product.template")?; + return Some(OwlPivot { + pivot_uri: row.pivot_uri, + family: row.family, + slot: row.slot, + dolce: dolce_odoo(class), + }); + } + None +} + +/// Both legs: resolve `class` to `(family, slot)` and confirm the slot is +/// **live** in the supplied family's hydrated table. Returns `None` when the +/// class is unmapped, the caller passed a different family's table, or the +/// slot is not (yet) hydrated. One static lookup + one hash probe — O(1). +pub fn resolve_odoo_to_family(class: &str, table: &OgitFamilyTable) -> Option<(OgitFamily, u16)> { + let pivot = resolve_odoo(class)?; + if pivot.family != table.family { + return None; + } + table + .lookup(pivot.identity()) + .map(|_| (pivot.family, pivot.slot)) +} + +/// Both legs, landing on the inline `FamilyEntry` (carries the DOLCE marker, +/// OWL characteristics, label, and provenance the harvest lanes read for the +/// inherited thinking style). Same O(1) guarantees as +/// [`resolve_odoo_to_family`]. +pub fn resolve_odoo_entry<'t>(class: &str, table: &'t OgitFamilyTable) -> Option<&'t FamilyEntry> { + let pivot = resolve_odoo(class)?; + if pivot.family != table.family { + return None; + } + table.lookup(pivot.identity()) +} + +/// Populate `table` with the odoo-aligned slots that belong to its family. +/// Idempotent. The TTL overlay path + tests call this so the leg-2 lookup in +/// [`resolve_odoo_to_family`] has live slots to confirm against. +pub fn seed_family_table(table: &mut OgitFamilyTable) { + for row in ODOO_SEED { + if row.family == table.family { + table.set(row.slot, row.entry()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn family_bytes_match_registry_ttl() { + // Option B binding: these MUST equal the familyId values in + // data/family_registry.ttl or the alignment lands in the wrong basin. + assert_eq!(FAMILY_BILLING_CORE.raw(), 97); + assert_eq!(FAMILY_SMB_ACCOUNTING.raw(), 98); + assert_eq!(FAMILY_SMB_FOUNDRY_CUSTOMER.raw(), 128); + assert_eq!(FAMILY_SMB_FOUNDRY_INVOICE.raw(), 129); + } + + #[test] + fn seed_rows_resolve_to_expected_pivots() { + assert_eq!( + resolve_odoo("res.partner").unwrap().pivot_uri, + "fibo:LegalEntity" + ); + assert_eq!( + resolve_odoo("account.move").unwrap().pivot_uri, + "fibo:Transaction" + ); + assert_eq!( + resolve_odoo("account.move.line").unwrap().pivot_uri, + "fibo:JournalEntryLine" + ); + assert_eq!( + resolve_odoo("account.account").unwrap().pivot_uri, + "fibo:Account" + ); + assert_eq!( + resolve_odoo("product.template").unwrap().pivot_uri, + "schema:Product" + ); + } + + #[test] + fn seed_rows_inherit_expected_families() { + assert_eq!( + resolve_odoo("res.partner").unwrap().family, + FAMILY_SMB_FOUNDRY_CUSTOMER + ); + assert_eq!( + resolve_odoo("account.move").unwrap().family, + FAMILY_SMB_FOUNDRY_INVOICE + ); + assert_eq!( + resolve_odoo("account.move.line").unwrap().family, + FAMILY_SMB_ACCOUNTING + ); + assert_eq!( + resolve_odoo("account.account").unwrap().family, + FAMILY_SMB_ACCOUNTING + ); + assert_eq!( + resolve_odoo("product.product").unwrap().family, + FAMILY_BILLING_CORE + ); + } + + #[test] + fn unmapped_classes_return_none() { + // The "needs a Layer-2 alignment axiom" signal — NOT a minted family. + assert!(resolve_odoo("stock.move").is_none()); + assert!(resolve_odoo("sale.order").is_none()); + assert!(resolve_odoo("hr.employee").is_none()); + assert!(resolve_odoo("account.reconcile.model").is_none()); + } + + #[test] + fn unseen_product_subtype_inherits_generic_slot() { + let p = resolve_odoo("product.category").expect("product.* prefix fallback"); + assert_eq!(p.pivot_uri, "schema:Product"); + assert_eq!(p.family, FAMILY_BILLING_CORE); + assert_eq!(p.slot, 1); // product.template's slot + } + + #[test] + fn dolce_marker_suffix_rules() { + assert_eq!(dolce_odoo("account.move"), DolceMarker::Perdurant); + assert_eq!(dolce_odoo("account.move.line"), DolceMarker::Perdurant); + assert_eq!(dolce_odoo("sale.order"), DolceMarker::Perdurant); + assert_eq!(dolce_odoo("account.tax"), DolceMarker::Abstract); + assert_eq!(dolce_odoo("account.fiscal.position"), DolceMarker::Abstract); + assert_eq!(dolce_odoo("res.partner"), DolceMarker::Endurant); + assert_eq!(dolce_odoo("account.account"), DolceMarker::Endurant); + assert_eq!(dolce_odoo("product.template"), DolceMarker::Endurant); + assert_eq!(dolce_odoo("ir.cron"), DolceMarker::Unknown); + } + + #[test] + fn end_to_end_against_live_table() { + // SMBAccounting basin: seed it, then chain odoo class → family/slot. + let mut table = OgitFamilyTable::empty(FAMILY_SMB_ACCOUNTING); + seed_family_table(&mut table); + assert!(!table.is_empty()); + + let (fam, slot) = resolve_odoo_to_family("account.account", &table).expect("live slot"); + assert_eq!(fam, FAMILY_SMB_ACCOUNTING); + assert_eq!(slot, 2); + + let entry = resolve_odoo_entry("account.move.line", &table).expect("live entry"); + assert_eq!(entry.label_uri, "ogit.SMBAccounting:JournalEntryLine"); + assert_eq!(entry.dolce_marker, DolceMarker::Perdurant); + } + + #[test] + fn wrong_family_table_yields_none() { + // res.partner inherits SmbFoundryCustomer; confirming against an + // SMBAccounting table must NOT match (and must not panic). + let mut table = OgitFamilyTable::empty(FAMILY_SMB_ACCOUNTING); + seed_family_table(&mut table); + assert!(resolve_odoo_to_family("res.partner", &table).is_none()); + assert!(resolve_odoo_entry("res.partner", &table).is_none()); + } + + #[test] + fn unhydrated_slot_is_not_live() { + // Right family, but the table was never seeded → leg-2 lookup misses. + let table = OgitFamilyTable::empty(FAMILY_SMB_ACCOUNTING); + assert!(resolve_odoo_to_family("account.account", &table).is_none()); + } +}