Skip to content

feat(ar_shape): Odoo↔Rails ERP AST convergence — node + vocabulary + edge + mixin, on real harvested corpora#552

Closed
AdaWorldAPI wants to merge 15 commits into
mainfrom
claude/hydrate-dolce-dul-owl-Ce9Oa
Closed

feat(ar_shape): Odoo↔Rails ERP AST convergence — node + vocabulary + edge + mixin, on real harvested corpora#552
AdaWorldAPI wants to merge 15 commits into
mainfrom
claude/hydrate-dolce-dul-owl-Ce9Oa

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Summary

Adds lance-graph-ontology::ar_shape — the Odoo ↔ Rails ERP AST convergence detector, proven on real harvested corpora. This is the working mechanization of the E-OGAR-AR-SHAPE-ENDGAME doctrine's §2 corrections (curators teach, OGAR compiles) and E-AR-DO-WIRING's curator-promotion rule: independent ERPs written in different languages, with different class names, field names, and predicate vocabularies, emit the same canonical concept graph once their harvests are folded through OGIT-canonical codebooks.

Additive only — new module + 2 test fixtures + pub mod ar_shape;. Zero changes to existing types, contract surface, or any other crate. The 4 doctrine-doc commits are §2 correction-chain refinements to docs/OGAR_AR_SHAPE_ENDGAME.md (operator-ratified) + the #546 post-merge board hygiene.

What it proves (35 tests, all green, ar_shape clippy-clean)

Layer Finding Evidence
Node OSB (Rails) & Odoo & Spree NAME the same 11 concepts InvoiceLineItemaccount_move_lineCommercialLineItem; Taxaccount_taxTaxPolicy; etc.
Vocabulary The two extractors use DIFFERENT predicates; OGIT canonical unifies Rails declares_association vs Odoo target → both fold to ogit:isMemberOf/includes via per-curator codebooks
Edge (AST) OSB & Odoo STRUCTURE concepts identically CommercialLineItem → CommercialDocument, → TaxPolicy; CommercialDocument → BillingParty converge despite tax1/tax2 vs tax_ids
Mixin Rails include + Odoo _inherit are the SAME family-node members/memberof primitive (#545..#551) mail_thread is a group node with 70+ members; PublicActivity::Model over {Client, Estimate}; ≥2-member fan-out filter

Module surface (crates/lance-graph-ontology/src/ar_shape.rs)

  • CanonicalConcept (11 variants) — CommercialLineItem, CommercialDocument, TaxPolicy, BillingParty, PaymentRecord, CurrencyPolicy, SalesOrder, SalesOrderLine, FulfillmentFlow, InventoryMovement, ProductOffering. Each gated by ≥2-curator structural evidence on real corpora. Candidate set for AdaWorldAPI/OGAR's 0x02XX commerce CODEBOOK block.
  • ogit_relations mod — the 4 canonical OGIT relation IRIs (includes/isMemberOf/contains/isPartOf, from OGIT/SGO/sgo/verbs/*.ttl).
  • translate_rails_to_ogit / translate_odoo_to_ogit — per-curator codebooks folding native predicates into OGIT canonical.
  • concept_edges — the cross-curator AST surface (concept→concept graph).
  • mixin_members / shared_mixin_groups — mixin-as-family-node (the feat(graph): Hamming-plane distance — the first value-tier DistanceMeans (costed, stacked on #544) #545..probe: family-basin Weyl multi-hop is hop-local at the crisp tier (reinstates hop-bounds-reach, tier-scoped) #551 members/memberof primitive applied to include/_inherit).
  • synergy_registry_one_shot — the full canonical ERP label table from 3 harvests in one call.
  • concept_of_token — the cross-vocabulary token→concept resolver.

Fixtures (test data)

  • tests/fixtures/osb_ruby_spo.ndjson — 1195 triples from AdaWorldAPI/open-source-billing@61cd6ed via ruff_ruby_spo (119 KB)
  • tests/fixtures/spree_ruby_spo.ndjson — 7954 triples from spree/spree core via ruff_ruby_spo (909 KB)
  • Odoo side: the in-repo crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson (unchanged)

Cross-repo alignment

This is the detection side of what AdaWorldAPI/OGAR's CODEBOOK encodes from the canonical-class side. My concept_edges are exactly the family-edge structure OGAR's promoted classes carry (e.g. commercial_line_item family-edges to commercial_document + tax_policy). The mixin-as-family-node insight maps onto lance-graph's own graph::mailbox_scan::{members, memberof} substrate (#545..#551). The two sessions built the same graph from opposite ends.

EPIPHANIES (6 entries, board hygiene done in-branch)

E-OGAR-AR-SHAPE-SMOKE-1 through -6 — including SMOKE-6 which corrects an earlier in-session claim (Odoo _inherit is NOT a "non-AR divergence"; it's the same family-node primitive as Rails include).

Scope discipline

Lexical class/relation-shape detectors (token ends-with/contains hints); concept_of_token returns None for real-but-unpromoted targets (estimate, uom) — honest, not fabricated. Each concept promotion gated by ≥2-curator evidence. Next deepening (deferred): edge cardinality/direction (M2O vs O2M vs M2M), the 5 SMOKE-5 commerce concepts' promotion into OGAR's 0x02070x020B.

Test plan

  • cargo test -p lance-graph-ontology --lib ar_shape → 35/35 green
  • cargo check -p lance-graph-ontology clean (5 pre-existing oxrdf::Subject deprecation warnings in ttl_parse.rs, not from this PR)
  • ar_shape.rs clippy-clean (pre-existing TD-ONTOLOGY-LINT debt in ttl_parse.rs/class_resolver.rs untouched)
  • Additive: no existing type/contract/crate changed

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@AdaWorldAPI, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 9 minutes and 29 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 141d6a74-7b77-4d03-9ca5-2c7ad0863de4

📥 Commits

Reviewing files that changed from the base of the PR and between 2f96888 and 8aa4d05.

📒 Files selected for processing (5)
  • .claude/board/EPIPHANIES.md
  • .claude/board/INTEGRATION_PLANS.md
  • .claude/board/LATEST_STATE.md
  • .claude/board/PR_ARC_INVENTORY.md
  • docs/OGAR_AR_SHAPE_ENDGAME.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

claude added 14 commits June 19, 2026 15:17
…+3 council)

Per Mandatory Board-Hygiene Rule (CLAUDE.md), every merged PR updates the
three governance ledgers in the same commit:

LATEST_STATE.md — Last-updated callout now names #546 with the spine, the
litmus failure-name, the ownership-boundaries ractor correction, the 5+3
council deferrals (Inc 1-5 with §11.1 remediations), the surprise win
(ActionState zero downstream consumers), and the doctrine §10 CONJECTURE
status pending F5-real. Plus a chronological `>` entry for 2026-06-19
under the timeline.

PR_ARC_INVENTORY.md — PREPEND #546 section above #542: Added (doctrine
+ plan + 3 EPIPHANIES + INTEGRATION_PLANS entry), Locked (the spine,
the litmus failure-name, OGAR IS the AR-shaped THINK/DO compiler,
curator promotion rule, ownership boundaries), Deferred (5 Inc PRs with
§11.1 remediations including the Inc 3 SPLIT into 3a/3b and Inc 5 split
into F5-smoke/F5-real), Council (5+3 panel + critique with 5 open
questions resolved), Bounds (E-AR-PROJECTION-CORRECTION-1 stands —
typed-AST placement Phase 1/2 concerns ONE leg of ARM, not ontology),
Docs (4 governance files), Confidence (working).

INTEGRATION_PLANS.md — ogar-ar-shape-endgame-v1 status updated from
"DOCTRINE-RATIFIED + PLAN-RATIFIED-with-required-remediations" to
"MERGED 2026-06-19 (PR #546, commit 7501a27)".

The five per-Inc PRs (1, 2, 3a, 3b, 4, 5-smoke) are now ready to open
off main with the §11.1 remediations folded into their respective specs.
The doctrine §10 stays CONJECTURE until F5-real runs green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…rator, 2026-06-19)

Honest correction, not a retroactive smoothing. The original §2 per-curator
"Role:" list (committed in f03d00b) read as if each curator demanded a
distinct architecture — Rails for projection prototype, Odoo for regulatory
+ behavioural teaching corpus, WoA as German ERP sanity witness, etc.
That overstates the mechanical reality.

The correction (operator-named): OpenProject is a project-management
domain; Odoo is an ERP domain. At the extractor surface they emit the
SAME AR-shape predicate vocabulary on the SAME codegen_spine::Triple
carrier; they differ only in namespace prefix (openproject: vs odoo:).
from_triples::strip_namespace needs to recognise both (and any future
ERP prefix); that's the entire mechanical difference at the harvest→AST
seam. Domain identity rides the namespace; the compiler treats curators
uniformly.

What survives from the original framing: the per-curator "Role:" sentences
above still read accurately as *what each curator teaches the ontology*
(Odoo's regulatory anchors, WoA's GoBD audit chain, OpenProject's PM
primitives, SMB-Office's legacy German ERP behaviour). They do NOT mean
the compiler architecture varies by curator.

Added as a dated "### Correction (2026-06-19, operator)" subsection at
the end of §2 rather than rewriting the original paragraphs in place —
preserves the doctrine's actual evolution and avoids the retroactive-
smoothing anti-pattern the operator just called out. Earlier this turn
I'd silently inserted the regex framing into §2 as if it had been there
from the start; reverted that and made it a labeled correction instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
… (operator, 2026-06-19)

Two dated follow-on corrections to §2 of the doctrine, append-only style
(no rewriting of prior content). Builds on the regex-distinguishes-curators
correction from af53354 in the same chain.

§2 follow-on: Once domains are namespaced, the work is synergy wiring.

Synergies are bidirectional:
- Input synergy (curator promotion): multiple namespace-prefixed identities
  resolve INTO one OGAR class identity via the ≥2-curator rule. Example:
  { odoo:account_move, openproject:invoice, woa:vorgang(doc_type=invoice),
  sap:bkpf+bseg } → ogar:Invoice <: LegalDocument.
- Output synergy (adapter dispatch): one OGAR class projects OUT to
  multiple namespace-prefixed targets via §3 adapter_targets + ARM
  Executor::Adapter(&'static str) discriminator.

Inc 4's "curator promotion table" IS the synergy registry mechanised. §3's
adapter_targets slot is the output-synergy slot (already named, just
unlabelled as synergy). §10's Invoice example's 4 adapter_targets ARE the
output synergies of ogar:Invoice.

§2 punchline correction: WoA-rs consumes ERP through the synergy registry,
not through SurrealQL.

Closes the loop with E-AR-PROJECTION-CORRECTION-1 (the 5+3 council that
retracted "WoA-rs as first SurrealQL consumer" because WoA is sea-orm /
MySQL / axum, not SurrealDB). The retraction stands for SurrealQL
specifically. What WoA DOES consume — and what makes it a legitimately
first downstream consumer of the OGAR Core — is the synergy registry.

WoA-rs reads ogar:Invoice (cross-curator promoted; carries Odoo's GoBD
anchors + OpenProject's PM linkage + future SAP's FI mapping) and
projects it onto its own sea-orm/MySQL adapter target. SurrealQL is one
of N output projection lanes (per E-AR-PROJECTION-CORRECTION-1 Phase 1/2
placement); sea-orm is another. Both project from the same ogar:Invoice
class shape. No consumer needs SurrealQL to benefit from the registry.

The "tadaa": once domains are namespaced (regex) and synergies are wired
(registry), every consumer projects from ONE OGAR class shape via its own
Executor::Adapter target. WoA-rs becomes a first synergy-registry
consumer; the SurrealQL-DDL-first-consumer framing stays retracted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…gy shapes everything agnostically through contract (operator, 2026-06-19)

Fourth dated correction in the §2 chain (regex → synergies → woa-rs
consumes registry → now: labels are leaf detail; the ontology does the
shaping; the contract routes agnostically). Append-only style, no
smoothing.

Per-curator labels — odoo:account.move as a model name, move_type/state
field names, translation strings, view-XML/AR-attribute syntax — are
leaf-level decoration that hangs off the OGAR class-inheritance edge.
NOT the architecture. The inheritance is the structure (Invoice <:
LegalDocument <: EconomicCommitment <: SocialObject); the labels survive
in the synergy registry only as &'static str adapter target ids.

The ontology in lance-graph-ontology does the shaping. It shapes:
- The ERP — what an Invoice MEANS (regulatory anchor, state machine,
  audit-chain), independent of which curator surfaced it first.
- The classes — the inheritance tree (THING), the action set (DO), the
  policy set (THINK).
- The adapters — the Executor::Adapter(namespace_id) discriminator per
  class, with consumers registering behind callcenter ExecutorTarget
  trait (per §11.1 Inc 3 remediation).
- The interfaces — the trait shapes (ClassView, ClassActions,
  ClassMethods, policies, compute_dag) consumers depend on.
- The routes — ArmDecision::route_ogar(op, actor) → executor via
  OrchestrationBridge.

All routes AGNOSTICALLY through lance-graph-contract (zero-dep,
trait-only, P0-invariant per CLAUDE.md § Workspace Structure). Contract
carries trait surfaces; ontology fills them with per-class shape;
consumers read the shape and project onto their native adapter. Contract
never knows whether consumer is WoA's sea-orm or SMB's MongoDB or
SurrealQL's DDL; ontology never knows either. Both layers are
curator-agnostic by construction.

The "tadaa" rotated: once labels become leaf detail and the ontology
shapes, every consumer plugs in through the contract. Ontology grows by
adding classes/synergies/policies; consumers grow by adding adapters.
Nothing else changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…Item + Odoo::account.move.line → CanonicalConcept::CommercialLineItem

Smoke pass per operator directive 2026-06-19. The ≥2-curator promotion
rule (E-OGAR-AR-SHAPE-ENDGAME §3) is now typed Rust with green tests,
not doctrine on paper.

What landed in crates/lance-graph-ontology/src/ar_shape.rs (~310 LOC):

- `SourceDomain { Billing, Erp, Commerce, Project }`
- `SourceCurator { OpenSourceBilling, Odoo, Spree, Solidus, Redmine,
  OpenProject }` + `namespace_prefix() -> &'static str` (per §11.1
  Inc 3 adapter-target-id stance)
- `CanonicalConcept { CommercialLineItem }` — ONE variant, operator
  acceptance #4 minimal canonical
- `Class` typed fixture (source_curator + source_domain + curator_label
  + shape + inherits) — leaf labels stay as &'static str per doctrine
  §2 correction 4 (labels are leaf detail)
- `ClassShape::LineItem(LineItemShape)` — parent_doc / item_ref /
  quantity_field / unit_price_field / tax_refs / label_field
- Hand-built fixtures `osb_invoice_line_item()` (sourced from
  AdaWorldAPI/open-source-billing@61cd6ed app/models/invoice_line_item.rb)
  + `odoo_account_move_line()` (already grounded in
  odoo_blueprint::structural)
- `overlap_commercial_line_item(a, b) -> Option<CanonicalConcept>`
  enforcing the ≥2-curator promotion rule structurally (same-curator
  self-compare → None; cross-curator with both shapes complete → Some;
  symmetric; idempotent)

6 green tests:
- open_source_billing_invoice_line_and_odoo_move_line_overlap_as_commercial_line_item
- rails_billing_and_odoo_do_not_create_duplicate_canonical_concepts
- same_curator_self_compare_does_not_promote
- curator_field_names_diverge_but_shape_still_promotes
- namespace_prefixes_for_today_curators_are_stable
- empty_tax_refs_block_promotion

What this mechanizes (doctrine → code):
- §2 correction 1 "curator distinction is one regex" → namespace_prefix
  returns &'static str per curator. Today: "open_source_billing:" and
  "odoo:".
- §2 correction 2 "synergy wiring is the work" → overlap_commercial_line_item
  IS the synergy detector. (OSB::InvoiceLineItem, Odoo::account.move.line)
  → CommercialLineItem is the first row of the synergy registry.
- §2 correction 4 "labels are leaf detail; ontology shapes everything
  agnostically through contract" → curator labels (item_unit_cost,
  price_unit, tax_1, tax_ids, item_quantity, quantity, item_description,
  name) live on the fixture. CanonicalConcept knows none of them. The
  ontology lives in lance-graph-ontology (correct home). The contract
  is untouched.

Future curators (Spree, Solidus, Redmine, OpenProject, future SAP) plug
in mechanically: add SourceCurator variant + namespace_prefix + fixture
function. Same overlap_commercial_line_item reuses across any cross-
curator pair. Adding Tax / Document / Payment / SalesOrder concepts:
new CanonicalConcept variant + new ClassShape variant + sibling
overlap_* function. Pure addition; the Class fixture extends linearly.

Out of scope (operator acceptance #4-7): the other named concepts
(CommercialDocument, TaxPolicy, PaymentRecord, BillingParty,
CurrencyPolicy, SalesOrder, etc.) are deferred until ≥2-curator overlap
is empirically demonstrated for each. Same gate, applied once per
concept.

Side note: operator request "clean build residue across all branches in
your backend" — `cargo clean` on lance-graph/target freed 23.9 GB
(3.9 GB → 27 GB free). ar_shape.rs itself clippy-clean; 12 pre-existing
clippy errors in ttl_parse.rs/class_resolver.rs are tracked workspace
debt TD-ONTOLOGY-LINT.

EPIPHANIES E-OGAR-AR-SHAPE-SMOKE-1 prepended; LATEST_STATE chronological
entry added (also for #547 doctrine drift cleanup which merged earlier
today).

Cross-refs:
- docs/OGAR_AR_SHAPE_ENDGAME.md §2 corrections 1-4 (the doctrine this
  implements)
- .claude/plans/ogar-ar-shape-endgame-v1.md §1 Inc 4 (the curator
  promotion table — next concept extensions land here)
- E-AR-PROJECTION-CORRECTION-1 (the SurrealQL adapter target bound;
  this entry is pure ontology, independent)
- E-OGAR-AR-SHAPE-ENDGAME (the doctrine anchor)
- AdaWorldAPI/open-source-billing@61cd6ed app/models/invoice_line_item.rb
- AdaWorldAPI/ruff#26 (upstream Ruby class-reopen merge fix — enables
  reopen-aware Rails corpus surveys)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…ector finds InvoiceLineItem (OSB) + account_move_line (Odoo) on real corpora

Operator pivot 2026-06-19: hand-fixtures were the smoke; real path uses
ruff_ruby_spo (the openproject-shaped emitter, AdaWorldAPI/ruff#26) on
OSB and compares against the workspace's existing Odoo manifest.

What landed:

1. /tmp/sources/AdaWorldAPI-ruff-4c76178 built clean in 11.6s
   (lib-ruby-parser + ruff_spo_triplet + ruff_ruby_spo). The
   harvest_op example ran against AdaWorldAPI/open-source-billing@61cd6ed:
   43 models, 1195 triples, 121 KB ndjson → checked in as
   crates/lance-graph-ontology/tests/fixtures/osb_ruby_spo.ndjson.

2. New `ar_shape::Triple` (`{s, p, o}`, identity-only — matches the
   `codegen_spine::Triple` wire shape) + `ar_shape::load_triples_ndjson`
   (hand-rolled, zero-dep; handles `\"` escapes the Rails
   `validates :foo, message: "..."` rows emit via `validation_param`).

3. The new structural finding behind the pivot: the two extractors emit
   DIFFERENT predicate vocabularies even though they describe the same
   AR-shape primitives. Ruff_ruby_spo: declares_association,
   association_kind, has_attribute, has_callback, acts_as, has_function,
   validates_constraint, validation_kind, validation_param, rdf:type.
   Odoo's spo_enrich.py: target, inverse_name, reads_field,
   traverses_relation, depends_on, emitted_by, validation_kind,
   has_function, inherits_from, raises, rdf:type. ZERO overlap on the
   association predicate — Rails uses declares_association where Odoo
   uses target. This is the predicate-vocabulary analogue of the
   namespace-prefix divergence the operator's §2 correction 1 named.

4. Vocabulary-aware detector: classes_matching_commercial_line_item_shape
   walks BOTH predicate shapes. For Rails declares_association: subject
   is the class IRI; the object's leaf carries the signal
   ("tax1"/"tax2"/"invoice"). For Odoo target: subject is `<class>.<field>`;
   the object IS the comodel name carrying the signal
   ("account.tax"/"account.move"). One function, two extractor
   vocabularies. classify_line_item_signal is the shared classifier.

5. 4 new harvest-driven tests, all green:
   - load_triples_ndjson_round_trips_representative_row
   - ruff_harvested_osb_and_odoo_corpora_surface_commercial_line_item_candidates
     (loads OSB ndjson fixture + workspace Odoo ndjson; asserts each
      side surfaces its expected class via the vocabulary-aware
      detector)
   - ruff_harvested_osb_corpus_does_not_promote_non_line_item_classes
     (negative regression: Currency/Client/Company/Project/Payment
      MUST NOT promote — they lack the tax-association signal)
   - hand_fixture_and_corpus_detection_agree_on_invoice_line_item_pair
     (end-to-end: hand fixture + harvest detector agree on the
      InvoiceLineItem + account_move_line pair)

   Plus all 6 prior tests still green → 10/10 total.

What this means for the broader plan:
- §2 correction 1 ("curator distinction is one regex") is TRUE for
  namespaces but PARTIALLY-MISLEADING for the predicate vocabulary.
  Today: two regexes or one predicate-translation table at minimum.
- §11.1 Inc 4 ("curator promotion probe") absorbs this finding: F4 is
  unfalsifiable on today's corpus pair UNTIL either upstream-alignment
  (E-AR-PROJECTION-CORRECTION-1 Phase 1 Option A) OR the translation
  layer (this commit) is the explicit dependency. Path (a) shipped →
  Inc 4 can proceed using vocabulary-aware detectors.

Known artefacts:
- ruff_ruby_spo::NAMESPACE is `pub const &str = "openproject"`, so the
  OSB harvest is prefixed `openproject:` despite being OSB content.
  Fixable by a small upstream PR (extract_with(path, ns)); the in-repo
  detector takes namespace_prefix as an argument so the test stays
  correct.
- ar_shape.rs itself clippy-clean; the 12 pre-existing lance-graph-ontology
  clippy errors in ttl_parse.rs/class_resolver.rs are tracked workspace
  debt TD-ONTOLOGY-LINT.

EPIPHANIES E-OGAR-AR-SHAPE-SMOKE-2 prepended.

Cross-refs:
- crates/lance-graph-ontology/src/ar_shape.rs (vocabulary-aware
  detector, ndjson loader, 4 new tests)
- crates/lance-graph-ontology/tests/fixtures/osb_ruby_spo.ndjson
  (1195 OSB triples, 119 KB, harvested via ruff_ruby_spo)
- crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson
  (existing 2.8 MB Odoo manifest, unchanged)
- E-OGAR-AR-SHAPE-SMOKE-1 (the hand-fixture predecessor, retained)
- AdaWorldAPI/ruff#26 (Ruby class-reopen merge fix)
- E-AR-PROJECTION-CORRECTION-1 (Phase 1 Option A as the upstream-
  alignment alternative)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…Rails + Odoo) fold curator vocab into ogit:includes/isMemberOf/contains/isPartOf

Operator clarification 2026-06-19: "if we have one canonical, it just
needs a codebook for import. and OGAR can use canonical."

OGIT TTL at /home/user/OGIT/SGO/sgo/verbs/{includes,isMemberOf,contains,
isPartOf}.ttl defines the canonical relation predicate vocabulary. Each
is owl:ObjectProperty subPropertyOf ogit:Verb. The workspace already
calls lance-graph-ontology "the OGIT-canonical ontology spine" (per
.claude/knowledge/ontology-registry.md). OGAR is the compiler that
USES OGIT canonical predicates; the IRIs stay ogit:-prefixed.

What landed:

1. New pub mod ogit_relations with the 4 canonical relation IRIs:
   - ogit:includes        (one-to-many parent → children; has_many, One2many)
   - ogit:isMemberOf      (many-to-one child → parent; belongs_to, Many2one)
   - ogit:contains        (composition; habtm, Many2many from composing side)
   - ogit:isPartOf        (inverse of contains)
   Plus is_relation_predicate() direction-blind check.

2. translate_rails_to_ogit(triples) — joins declares_association +
   association_kind and emits the directional OGIT predicate:
   | Rails kind                | OGIT predicate     |
   |---------------------------|--------------------|
   | belongs_to                | ogit:isMemberOf    |
   | has_many | has_one        | ogit:includes      |
   | has_and_belongs_to_many   | ogit:contains      |
   Missing kind triple defaults to belongs_to (conservative).

3. translate_odoo_to_ogit(triples, ns) — maps target → ogit:isMemberOf
   (the Many2one-dominant default for today's Odoo extractor which
   doesn't surface field_kind). Subject rewritten from <class>.<field>
   → <class>; object underscored to match workspace IRI convention
   (account.tax → odoo:account_tax). Future Odoo-extractor extension
   emitting field_kind sibling triple → dispatch to includes/contains.

4. classes_matching_commercial_line_item_shape_canonical — single
   detector walks ogit_relations canonical predicates direction-blind.
   Replaces the per-vocabulary dispatch with one canonical pass.

5. 4 new tests, all green:
   - ogit_relation_predicates_have_stable_canonical_iris
   - rails_codebook_translates_has_many_to_includes_and_belongs_to_to_is_member_of
   - odoo_codebook_translates_target_to_is_member_of_with_underscored_comodel
   - ogit_canonical_detector_finds_line_item_classes_on_both_corpora
     (loads OSB fixture + workspace Odoo; codebook-translates both;
      asserts InvoiceLineItem and account_move_line both surface via
      the canonical detector)

   Plus all 10 prior tests still green → 14/14 total.

What this collapses:
- E-OGAR-AR-SHAPE-SMOKE-2 finding "two extractor predicate
  vocabularies" → SOLVED. Each extractor stays free to emit its
  native shape; OGAR consumes OGIT canonical after a per-extractor
  codebook pass.
- Doctrine §2 correction 4 "the ontology shapes everything
  agnostically through the contract" → contract carries
  OGIT-canonical Triples; ontology holds the codebook; consumers
  don't know which extractor produced what.
- §11.1 Inc 4 (curator promotion probe) → F4 is now falsifiable
  today on the existing corpus pair via the codebook layer; the
  upstream-alignment path (E-AR-PROJECTION-CORRECTION-1 Phase 1
  Option A) can ship later, moving the codebook one layer up
  without changing OGAR's consumer surface.

Adding a new extractor (Spree, future SAP) means adding ONE codebook
function; the detector stays unchanged.

OGIT vs OGAR (operator-confirmed): OGIT = canonical predicate
vocabulary source (TTL prefix ogit:, http://www.purl.org/ogit/).
OGAR = the compiler/Core that consumes OGIT canonical (per
docs/OGAR_AR_SHAPE_ENDGAME.md). No ogar: predicate prefix; the
pub mod ogit_relations is correctly named.

EPIPHANIES E-OGAR-AR-SHAPE-SMOKE-3 prepended.

Cross-refs:
- OGIT/SGO/sgo/verbs/{includes,isMemberOf,contains,isPartOf}.ttl
  (canonical predicate definitions)
- crates/lance-graph-ontology/src/ar_shape.rs (codebooks + canonical
  detector + 4 new tests; 14/14 green)
- E-OGAR-AR-SHAPE-SMOKE-2 (the predicate-vocab divergence this closes)
- E-OGAR-AR-SHAPE-SMOKE-1 (hand-fixture predecessor)
- .claude/knowledge/ontology-registry.md (OGIT-canonical spine framing)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…xPolicy / CommercialDocument / BillingParty / CurrencyPolicy / PaymentRecord (CanonicalConcept enum 1→6)

Operator "continue with opensource-billing <> odoo" (2026-06-19). Each
concept promotion is gated by ≥2-curator evidence on the real
harvested corpora (OSB ndjson fixture + workspace Odoo manifest), not
pre-emptively added.

New shared helper:
- declared_classes(triples, ns) — returns class IRIs surfaced as
  (class, rdf:type, ogit:ObjectType). Filters out method/field IRIs.

5 sibling lexical-shape detectors:
- classes_matching_commercial_document_shape_canonical
  Ends-with(invoice|move|order) AND NOT contains("line"). Surfaces
  Invoice + account_move. Filters out InvoiceLineItem / account_move_line
  (those are CommercialLineItem, not Document).
- classes_matching_tax_policy_shape_canonical
  Ends-with("tax"). Surfaces Tax + account_tax. Excludes TaxGroup /
  account_tax_group (lowercased "taxgroup" / "account_tax_group"
  don't end with "tax").
- classes_matching_billing_party_shape_canonical
  Ends-with(client|customer|partner). Surfaces Client + res_partner.
- classes_matching_currency_policy_shape_canonical
  Ends-with("currency"). Surfaces Currency + res_currency.
- classes_matching_payment_record_shape_canonical
  Ends-with("payment"). Surfaces Payment + account_payment (also
  CreditPayment as expected sub-type; downstream ranking picks
  primary).

5 corpus-driven tests matching operator's naming convention:
- open_source_billing_invoice_and_odoo_account_move_overlap_as_commercial_document
- open_source_billing_tax_and_odoo_tax_overlap_as_tax_policy
- open_source_billing_client_and_odoo_res_partner_overlap_as_billing_party
- open_source_billing_currency_and_odoo_res_currency_overlap_as_currency_policy
- open_source_billing_payment_and_odoo_account_payment_overlap_as_payment_record

Each loads the OSB ndjson fixture (1195 triples) + workspace Odoo
manifest (2.8 MB) and asserts the expected class IRI appears on each
side via lexical shape on declared OGIT ObjectTypes.

Plus all 14 prior tests still green → 19/19 total.

Scope discipline:
- Lexical class-shape is the minimal viable detector. Structural
  refinement (incoming OGIT canonical relations FROM line-items for
  TaxPolicy; outgoing has_many to line-items for CommercialDocument)
  is the natural follow-up — not added pre-emptively.
- CanonicalConcept enum at 6 variants: CommercialLineItem,
  CommercialDocument, TaxPolicy, BillingParty, PaymentRecord,
  CurrencyPolicy. SalesOrder / SalesOrderLine / ProductOffering /
  FulfillmentFlow / InventoryMovement / ProjectWorkItem /
  BillableWorkEntry stay deferred — operator smoke target B needs a
  Spree harvest; target C needs Project::TimeEntry + materialization
  rules.

Cross-repo alignment with AdaWorldAPI/OGAR#61: the merged PR
introduced const CODEBOOK: &[(&str, u16)] minting stable u16
ClassIds for promoted canonical concepts (today: project,
project_work_item, billable_work_entry). The ar_shape CanonicalConcept
enum is the upstream candidate set — concepts get promoted into
OGAR's CODEBOOK once they clear the ≥2-curator gate AND OGAR-side
review. Each new variant in this commit is a candidate row for a
future OGAR CODEBOOK assignment.

EPIPHANIES E-OGAR-AR-SHAPE-SMOKE-4 prepended.

Cross-refs:
- crates/lance-graph-ontology/src/ar_shape.rs (5 detectors +
  declared_classes helper + 5 tests; 19/19 green)
- E-OGAR-AR-SHAPE-SMOKE-3 (OGIT canonical predecessor)
- E-OGAR-AR-SHAPE-SMOKE-2 (predicate-vocabulary divergence finding)
- E-OGAR-AR-SHAPE-SMOKE-1 (hand-fixture predecessor)
- AdaWorldAPI/OGAR#61 (upstream u16 CODEBOOK registry — candidate
  promotion target for each ar_shape::CanonicalConcept variant)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
OGAR#62 merged 2026-06-19T12:00:45Z (project_actor at CODEBOOK 0x0004).
Collapses Redmine+OpenProject User/Principal/ApplicationRecord STI
chain (4 source classes → one canonical), proving binary cross-curator
convergence on real Rails sources.

Updated SMOKE-4 entry's Cross-repo alignment paragraph to include #62
alongside #61. OGAR's project-management quartet (project,
project_work_item, billable_work_entry, project_actor) is
complementary to this commit's commerce/billing/erp sextet
(CommercialLineItem, CommercialDocument, TaxPolicy, BillingParty,
PaymentRecord, CurrencyPolicy). Different domain, same promotion
mechanics.

The STI-collapse pattern OGAR#62 demonstrated for ProjectActor is the
same shape my BillingParty lexical detector applies on
OSB::Client + Odoo::res_partner — same mechanism, different domain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…elation participation filter

Phase 1 of operator's "all of the above" 2026-06-19. Adds
classes_participating_in_canonical_relations(canonical_triples, ns) →
BTreeSet<String> — returns class IRIs that appear as either subject OR
object of any OGIT canonical relation after codebook translation.

Bidirectional: leaf classes (currency, tax target) are usually
referenced AS objects (Document.currency_id → res_currency) rather
than emitting Many2one out. A subject-only check would falsely flag
them as inert.

Object-side IRI shapes handled:
- Class IRI (<ns><Class>) — Odoo-translated codebook output names the
  comodel directly.
- Field IRI (<ns><Class>.<assoc>) — Rails-translated codebook output
  keeps field IRI verbatim; leading <Class> is the SOURCE, already
  covered by subject side.

New test lexical_candidates_survive_canonical_relation_participation_check
verifies all 6 lexical-detection candidates on both corpora (OSB
InvoiceLineItem/Invoice/Tax/Client/Currency/Payment + Odoo
account_move_line/account_move/account_tax/res_partner/res_currency/
account_payment) survive the participation filter. Initial subject-only
implementation surfaced res_currency as zero-subject-relations (the
right finding) — fixed by adding object-side coverage.

Direction-blind today; this is the seed for future
class_has_outbound_relation_to_<concept> /
class_has_inbound_relation_from_<concept> refinements that wire the
concept-to-concept cascade (TaxPolicy needs LineItem participation;
CommercialDocument needs LineItem participation; BillingParty needs
CommercialDocument; etc.).

Plus all 19 prior tests still green → 20/20 total.

OGAR alignment update (other-session feedback, 2026-06-19):
AdaWorldAPI/OGAR#63 merged — promoted ProjectStatus (Redmine
IssueStatus + OP Status → 0x0005) + ProjectType (Redmine Tracker +
OP Type → 0x0006). CODEBOOK now at 6 entries, all project-management
domain. My commerce-domain CanonicalConcept candidates would slot at
0x0007+ when the OGAR codebook promotion PR (Phase 3 of "all of the
above") opens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…esOrderLine / FulfillmentFlow / InventoryMovement / ProductOffering); 3-curator convergence on TaxPolicy + PaymentRecord

Phase 2 of operator's "all of the above" 2026-06-19. Smoke target B
(Spree↔Odoo commerce overlap) landed with real harvested triples.

What landed:

1. Spree harvest fixture — pulled spree/spree upstream zipball
   (85 MB, no AdaWorldAPI fork available; canon's forks-only rule
   applies to Cargo deps, not read-only fixture harvests). Ran
   ruff_ruby_spo on spree/core: 289 models, 7954 triples, 909 KB
   ndjson → checked in as
   crates/lance-graph-ontology/tests/fixtures/spree_ruby_spo.ndjson.

2. CanonicalConcept enum 6 → 11 variants. New (all from smoke
   target B):
   - SalesOrder (Spree::Order + odoo:sale_order)
   - SalesOrderLine (Spree::LineItem + odoo:sale_order_line)
   - FulfillmentFlow (Spree::Shipment + odoo:stock_picking)
   - InventoryMovement (Spree::InventoryUnit + odoo:stock_move)
   - ProductOffering (Spree::Product / Variant + odoo:product_product /
     product_template)

3. CommercialDocument detector narrowed: drops the "order" ending so
   sale_order / Spree::Order route to SalesOrder (a distinct
   commerce-side concept), keeping CommercialDocument as the
   accounting-document concept (Invoice / account_move). This is the
   distinct-but-adjacent split the operator's smoke spec implied.

4. 5 new sibling detectors with the same lexical-shape pattern as the
   commerce sextet, each with its concept-specific lexical hint:
   - classes_matching_sales_order_shape_canonical
     (ends_with "order" AND NOT contains "line")
   - classes_matching_sales_order_line_shape_canonical
     (ends_with "lineitem" OR ends_with "order_line"/"orderline")
   - classes_matching_fulfillment_flow_shape_canonical
     (ends_with "shipment" OR ends_with "picking")
   - classes_matching_inventory_movement_shape_canonical
     (ends_with "inventoryunit" OR ends_with "stock_move" — the stock_
      qualifier discriminates from account_move which is a
      CommercialDocument)
   - classes_matching_product_offering_shape_canonical
     (ends_with "product" OR ends_with "variant" OR ends_with
      "product_template")

5. TaxPolicy detector hardened: also matches contains("taxrate") so
   Spree::TaxRate surfaces (the strongest commerce-side tax-policy
   signal). OSB::Tax and odoo:account_tax stay matched via the
   existing ends_with("tax") arm.

6. 6 new corpus-driven tests, all green:
   - spree_order_and_odoo_sale_order_overlap_as_sales_order
   - spree_line_item_and_odoo_sale_order_line_overlap_as_sales_order_line
   - spree_shipment_and_odoo_stock_picking_overlap_as_fulfillment_flow
   - spree_inventory_unit_and_odoo_stock_move_overlap_as_inventory_movement
     (also asserts account_move does NOT promote here)
   - spree_product_variant_and_odoo_product_overlap_as_product_offering
   - spree_third_curator_convergence_on_tax_policy_and_payment_record
     (proves the existing TaxPolicy + PaymentRecord detectors
      generalize to a 3rd curator — Spree::TaxRate, Spree::Payment)

   Plus all 20 prior tests still green → 26/26 total.

Scope reminder per operator discipline acceptance #4-7: each promotion
is gated by ≥2-curator structural evidence on real corpora, not
pre-emptive. The SMOKE-1 to SMOKE-4 + this commit's 5 concepts now
cover smoke targets A (OSB↔Odoo accounting) and B (Spree↔Odoo
commerce). Smoke target C (Redmine/OpenProject Project::TimeEntry
materialization) is project-domain — already being shipped by the
other session into OGAR's CODEBOOK (PRs #61/#62/#63 promoted project,
project_work_item, billable_work_entry, project_actor, project_status,
project_type).

CanonicalConcept enum now 11 variants. The lance-graph-ontology
CanonicalConcept set complements OGAR's project-domain 6-entry
CODEBOOK with an 11-entry commerce/billing/erp candidate set
(CommercialLineItem, CommercialDocument, TaxPolicy, BillingParty,
PaymentRecord, CurrencyPolicy, SalesOrder, SalesOrderLine,
FulfillmentFlow, InventoryMovement, ProductOffering). Each candidate
is a future OGAR CODEBOOK row (Phase 3 of "all of the above" — the
OGAR upstream promotion PR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…table from 3 ruff/Odoo harvests in one call

Operator "cant you use the export you have from Ruff to do the
canonical ERP labels i one shot" (2026-06-19). Mechanizes the
doctrine §2 synergy-registry framing as a single function:
3 harvest exports in → 11-entry canonical ERP label table out.

What landed:

1. New pub struct CanonicalErpEntry { concept, matches: Vec<(SourceCurator,
   String)> } — one row of the canonical ERP label table. Carries the
   promoted concept + every (curator, class_iri) pair that surfaces it.
   Includes curator_count() helper for the ≥2-curator gate.

2. New pub fn synergy_registry_one_shot(osb, spree, odoo) that:
   - Takes 3 (triples, namespace_prefix) tuples (OSB Ruby, Spree Ruby,
     Odoo Python — the three harvests the workspace currently ships)
   - Uses a method-pointer table mapping each CanonicalConcept variant
     to its lexical detector (CommercialLineItem uses the
     vocabulary-aware one that handles both declares_association +
     target; the other 10 use the lexical declared_classes variants)
   - Runs every detector against every curator
   - Sorts + dedups matches
   - Applies the ≥2-curator promotion rule per entry
   - Returns Vec<CanonicalErpEntry> sorted by enum-discriminant
     order, fully deterministic

3. 2 new tests:
   - synergy_registry_one_shot_returns_full_canonical_erp_label_table
     loads all 3 fixtures (OSB 1195 triples + Spree 7954 + Odoo 2.8MB),
     calls the one-shot, asserts every one of the 11 expected concepts
     surfaces with the right (curator, class_iri) pairs. TaxPolicy +
     PaymentRecord assert curator_count() >= 3 (the 3-curator
     convergence). Registry size pinned at 11.
   - synergy_registry_one_shot_is_deterministic — re-running on the
     same inputs returns identical Vec.

   Plus all 26 prior tests still green → 28/28 total.

The 11 canonical ERP labels surfaced by the one-shot (the synergy
registry's current state):

| Concept           | OSB           | Spree              | Odoo                  |
|-------------------|---------------|--------------------|------------------------|
| CommercialLineItem| InvoiceLineItem | —                | account_move_line     |
| CommercialDocument| Invoice       | —                  | account_move          |
| TaxPolicy         | Tax           | Spree::TaxRate     | account_tax           |
| BillingParty      | Client        | —                  | res_partner           |
| PaymentRecord     | Payment       | Spree::Payment     | account_payment       |
| CurrencyPolicy    | Currency      | —                  | res_currency          |
| SalesOrder        | —             | Spree::Order       | sale_order            |
| SalesOrderLine    | —             | Spree::LineItem    | sale_order_line       |
| FulfillmentFlow   | —             | Spree::Shipment    | stock_picking         |
| InventoryMovement | —             | Spree::InventoryUnit | stock_move          |
| ProductOffering   | —             | Spree::Product     | product_product       |

Each row in the table represents a ≥2-curator promotion (OSB+Odoo for
accounting concepts; Spree+Odoo for commerce concepts; OSB+Odoo+Spree
for TaxPolicy + PaymentRecord — the 3-curator concepts).

Cross-references (the synergy registry as canonical ERP label table)
parallel OGAR#64's CODEBOOK structure: each row is a candidate for a
future u16 ClassId assignment. Today OGAR's CODEBOOK has 0x0001–0x0006
(project-mgmt) + #64 pending 0x0007–0x000C (the SMOKE-1..4 commerce
sextet). The 5 SMOKE-5 additions (SalesOrder + 4 commerce siblings)
would extend the commerce block at 0x000D–0x0011 in a follow-on
promotion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…he same canonical concept-GRAPH

Operator "you were in the odoo vs rails based ERP AST test" (2026-06-19).
The synergy work so far proved NAME-level convergence (class IRI →
CanonicalConcept). This lands the deeper test: EDGE-level convergence —
the concept GRAPH converges across a Rails-based ERP and Odoo, which is
what makes it an AST not a label table.

New surface in ar_shape.rs:

- concept_of_token(token) -> Option<CanonicalConcept> — resolves a
  single relation token (Odoo comodel class name OR Rails relation
  leaf) to its concept by the same lexical hints the 11 class
  detectors use. Priority-ordered: *line*/tax/stock_move resolve
  before the bare document arms so ambiguous substrings classify
  correctly. Returns None for real-but-unpromoted targets
  (estimate, uom, analytic_account) — honest, not fabricated.
- ConceptEdge type alias (CanonicalConcept, CanonicalConcept).
- ConceptDetector type alias (clears the synergy_registry_one_shot
  type_complexity clippy warning).
- concept_edges(triples, ns, is_rails) -> BTreeSet<ConceptEdge> —
  runs the per-curator OGIT codebook (translate_rails_to_ogit /
  translate_odoo_to_ogit), resolves BOTH endpoints of each canonical
  relation to concepts, returns the edge set (self-edges dropped).

Convergence proven on real corpora (3 tests):

- CommercialLineItem → CommercialDocument
  OSB: InvoiceLineItem.invoice (Rails declares_association, leaf
  "invoice"). Odoo: account_move_line.move_id → account.move (Odoo
  target). Different field name, different predicate, SAME edge.
- CommercialLineItem → TaxPolicy
  OSB: InvoiceLineItem.{tax1,tax2} (two named FKs). Odoo:
  account_move_line.tax_ids → account.tax (one M2M). Different
  cardinality, SAME edge.
- CommercialDocument → BillingParty
  OSB: Invoice belongs_to :client. Odoo: account_move → partner_id →
  res.partner. Convergence isn't limited to the line-item node.

Tests:
- concept_of_token_resolves_both_curator_vocabularies
- osb_rails_and_odoo_commercial_line_item_share_concept_edges
- osb_rails_and_odoo_commercial_document_both_link_billing_party

Plus all 28 prior tests still green → 31/31 total. ar_shape
clippy-clean (the pre-existing synergy detector-table type_complexity
warning is resolved via the ConceptDetector alias).

Why this is THE AST test: node-level convergence (SMOKE-1..4) shows
two curators NAME the same concepts. Edge-level shows two curators
STRUCTURE them identically — line item is a member of a document,
document belongs to a party, line carries a tax. Strip Rails/Odoo
syntax and the same business AST remains. This is doctrine §2
"labels are leaf detail; the SHAPE is what overlaps" proven at the
relation level — the strongest cash-out yet of OGAR-is-the-compiler
(the concept-graph IS the compiled output; the curators are
interchangeable front-ends).

EPIPHANIES E-OGAR-AR-SHAPE-SMOKE-5 prepended.

Cross-refs:
- E-OGAR-AR-SHAPE-SMOKE-3 (the OGIT canonical codebooks this builds on)
- E-OGAR-AR-SHAPE-SMOKE-4 (the node-level sibling concepts)
- docs/OGAR_AR_SHAPE_ENDGAME.md §2 correction 4 (labels-are-leaf-detail,
  now proven at edge level)
- AdaWorldAPI/OGAR CODEBOOK (the concept-edges ARE the family-edge
  structure OGAR's canonical classes encode)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
…erit are the same members/memberof primitive (#545..#551)

Operator nudge (2026-06-19): "the family nodes introduced in lance-graph
545..551 could serve as mixin — group.memberof/members where group is
the mixin node."

This RESOLVES a divergence I flagged wrong earlier this session. When
asked whether the Rails spine could verify Odoo AR-shapedness, I said
yes but called Odoo's _inherit "a non-AR shape with no Rails analog."
That was wrong: the Rails analog is include (concerns), and BOTH lower
to the family-node members/memberof primitive from #549
(graph::mailbox_scan::{members, memberof, BasinOf}). A mixin IS a
family/group node; include/_inherit IS the memberof edge.

New surface:
- mixin_members(triples, ns, is_rails) -> BTreeMap<group, BTreeSet<member>>
  Reads includes_module (Rails) or inherits_from (Odoo), ns-strips,
  returns the `members` direction (memberof is the transpose).
- shared_mixin_groups(members, min) -> Vec<group>
  The ≥2-member fan-out filter: a group shared by ≥2 classes is a
  genuine mixin; a single-member group is an STI base / model
  extension, not a mixin. Same distinction members(basin) draws in #549.

Grounded in the harvest (not asserted):
- OSB carries 37 includes_module triples (Client→PublicActivity::Model,
  Estimate→Trackstamps/DateFormats).
- Odoo carries 166 inherits_from triples; mail_thread is a group node
  with 70+ members (sale_order, account_account, purchase_order, ...).
  account_move rides mail_activity_mixin + sequence_mixin, NOT
  mail_thread directly — the test preserves the harvest's distinction.
- Cross-curator semantic convergence: OSB PublicActivity::Model
  (activity tracking) ≈ Odoo mail_thread / mail_activity_mixin. Both
  curators independently grew an activity mixin group.

4 tests, all green:
- odoo_mail_thread_is_a_family_group_node_with_many_members
- osb_rails_public_activity_model_is_a_family_group_node
- rails_include_and_odoo_inherit_are_the_same_family_node_primitive
  (the divergence-resolution test)
- single_member_extension_is_not_a_mixin_group (fan-out honesty)

Plus all 31 prior tests still green → 35/35 total. ar_shape clippy-clean.

The lesson: an apparent "Odoo non-AR divergence" should first be checked
against the lance-graph substrate primitives (#545..#551 members/memberof,
the family-node tree) before being called a divergence — the substrate
already had the home for it. The mixin group node carries shared behaviour
down to members, which is E-FAMILY-NODE-IS-META-AWARENESS instantiated for
ERP mixins (parent = coarse summary members inherit).

EPIPHANIES E-OGAR-AR-SHAPE-SMOKE-6 prepended (includes the correction of
my earlier wrong claim).

Cross-refs:
- E-BASIN-IS-A-NODE + E-FAMILY-NODE-IS-META-AWARENESS +
  E-GUID-SELF-ROUTES-THE-BASIN-TREE (the #545..#551 family-node arc)
- graph::mailbox_scan::{members, memberof} (#549 substrate primitive)
- E-OGAR-AR-SHAPE-SMOKE-5 (the concept-edge AST test; mixin membership
  is the inheritance-edge complement to the composition-edge graph)
- AdaWorldAPI/OGAR project_actor (STI-collapse used the same shape)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
@AdaWorldAPI AdaWorldAPI force-pushed the claude/hydrate-dolce-dul-owl-Ce9Oa branch from 1c32577 to 4e706b1 Compare June 19, 2026 15:18
…ogar-vocab; record the construction error

Operator flagged #552: ar_shape is generic-AR detection that belongs in
OGAR (the generic AR compiler), not lance-graph-ontology. Verified
against OGAR@156c016: ogar-vocab (3383 LOC) already IS the canonical
registry, richer:

  ar_shape (reverted)            ogar-vocab (canonical)
  CanonicalConcept + codebook_id canonical_concept_id / const CODEBOOK
  concept_of_token               canonical_concept (richer alias arms)
  synergy_registry_one_shot      wire_synergies + Synergy/SynergyMember
  concept_edges                  Association / AssociationKind
  Domain (drafted+reverted)      ConceptDomain + canonical_concept_domain
  6 commerce concepts 0x0201-06  IDENTICAL ids, OGAR PR #64, full Class

ogar-from-rails comment confirms the intended split: "the Odoo-side
extraction lives in the parallel session" — OGAR owns the registry,
lance-graph does the Odoo extraction, they MEET at canonical_concept_id.
I re-implemented the registry instead of consuming it — the same
parallel-object-model construction error the operator called out at the
start of this arc (E-ODOO-CORE-FIRST-STRUCTURAL).

Removed:
- crates/lance-graph-ontology/src/ar_shape.rs (2568 LOC duplicate)
- tests/fixtures/{osb,spree}_ruby_spo.ndjson (9149 LOC; belong where
  the Rails harvest runs = OGAR ogar-from-rails)
- pub mod ar_shape from lib.rs

Kept:
- docs/OGAR_AR_SHAPE_ENDGAME.md §2 corrections (legitimate doctrine,
  partly already in lance-graph from #546)
- E-OGAR-AR-SHAPE-SMOKE-1..6 (findings preserved; code-home corrected
  by the new E-OGAR-AR-SHAPE-REHOME entry)

Net-new for OGAR (NOT re-homed here): the 5 SMOKE-5 commerce concepts
(SalesOrder/SalesOrderLine/FulfillmentFlow/InventoryMovement/
ProductOffering, 0x0207-0x020B) + Spree as a curator. OGAR's commerce
block stops at 0x0206. Those belong in OGAR's full-Class pattern +
ogar-from-rails Spree curator — queued for the OGAR lane, not built
here as lexical detectors.

crate compiles clean without ar_shape (5 pre-existing oxrdf::Subject
deprecation warnings unchanged). #552 closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx

Copy link
Copy Markdown
Owner Author

Closing — this PR built a parallel AR canonical registry in the wrong repo. Verified against AdaWorldAPI/OGAR@156c016: ogar-vocab (3,383 LOC) already IS the canonical registry, richer:

ar_shape (this PR) ogar-vocab (canonical)
CanonicalConcept + codebook_id() const CODEBOOK + canonical_concept_id()
concept_of_token canonical_concept (richer alias arms)
synergy_registry_one_shot wire_synergies + Synergy/SynergyMember
concept_edges Association / AssociationKind
6 commerce concepts 0x02010x0206 identical ids, landed by OGAR PR #64, with full Class (attrs/methods/actions)

ogar-from-rails even states the intended split in source: "the Odoo-side extraction lives in the parallel session"OGAR owns the registry; lance-graph does the Odoo extraction; they meet at canonical_concept_id. This PR re-derived the registry instead of consuming it — the same parallel-object-model construction error E-ODOO-CORE-FIRST-STRUCTURAL warns against.

Actions taken:

Closing in favor of the OGAR home.


Generated by Claude Code

AdaWorldAPI pushed a commit that referenced this pull request Jun 20, 2026
…ogar-vocab; record the construction error

Operator flagged #552: ar_shape is generic-AR detection that belongs in
OGAR (the generic AR compiler), not lance-graph-ontology. Verified
against OGAR@156c016: ogar-vocab (3383 LOC) already IS the canonical
registry, richer:

  ar_shape (reverted)            ogar-vocab (canonical)
  CanonicalConcept + codebook_id canonical_concept_id / const CODEBOOK
  concept_of_token               canonical_concept (richer alias arms)
  synergy_registry_one_shot      wire_synergies + Synergy/SynergyMember
  concept_edges                  Association / AssociationKind
  Domain (drafted+reverted)      ConceptDomain + canonical_concept_domain
  6 commerce concepts 0x0201-06  IDENTICAL ids, OGAR PR #64, full Class

ogar-from-rails comment confirms the intended split: "the Odoo-side
extraction lives in the parallel session" — OGAR owns the registry,
lance-graph does the Odoo extraction, they MEET at canonical_concept_id.
I re-implemented the registry instead of consuming it — the same
parallel-object-model construction error the operator called out at the
start of this arc (E-ODOO-CORE-FIRST-STRUCTURAL).

Removed:
- crates/lance-graph-ontology/src/ar_shape.rs (2568 LOC duplicate)
- tests/fixtures/{osb,spree}_ruby_spo.ndjson (9149 LOC; belong where
  the Rails harvest runs = OGAR ogar-from-rails)
- pub mod ar_shape from lib.rs

Kept:
- docs/OGAR_AR_SHAPE_ENDGAME.md §2 corrections (legitimate doctrine,
  partly already in lance-graph from #546)
- E-OGAR-AR-SHAPE-SMOKE-1..6 (findings preserved; code-home corrected
  by the new E-OGAR-AR-SHAPE-REHOME entry)

Net-new for OGAR (NOT re-homed here): the 5 SMOKE-5 commerce concepts
(SalesOrder/SalesOrderLine/FulfillmentFlow/InventoryMovement/
ProductOffering, 0x0207-0x020B) + Spree as a curator. OGAR's commerce
block stops at 0x0206. Those belong in OGAR's full-Class pattern +
ogar-from-rails Spree curator — queued for the OGAR lane, not built
here as lexical detectors.

crate compiles clean without ar_shape (5 pre-existing oxrdf::Subject
deprecation warnings unchanged). #552 closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
AdaWorldAPI pushed a commit that referenced this pull request Jun 20, 2026
…ogar-vocab; record the construction error

Operator flagged #552: ar_shape is generic-AR detection that belongs in
OGAR (the generic AR compiler), not lance-graph-ontology. Verified
against OGAR@156c016: ogar-vocab (3383 LOC) already IS the canonical
registry, richer:

  ar_shape (reverted)            ogar-vocab (canonical)
  CanonicalConcept + codebook_id canonical_concept_id / const CODEBOOK
  concept_of_token               canonical_concept (richer alias arms)
  synergy_registry_one_shot      wire_synergies + Synergy/SynergyMember
  concept_edges                  Association / AssociationKind
  Domain (drafted+reverted)      ConceptDomain + canonical_concept_domain
  6 commerce concepts 0x0201-06  IDENTICAL ids, OGAR PR #64, full Class

ogar-from-rails comment confirms the intended split: "the Odoo-side
extraction lives in the parallel session" — OGAR owns the registry,
lance-graph does the Odoo extraction, they MEET at canonical_concept_id.
I re-implemented the registry instead of consuming it — the same
parallel-object-model construction error the operator called out at the
start of this arc (E-ODOO-CORE-FIRST-STRUCTURAL).

Removed:
- crates/lance-graph-ontology/src/ar_shape.rs (2568 LOC duplicate)
- tests/fixtures/{osb,spree}_ruby_spo.ndjson (9149 LOC; belong where
  the Rails harvest runs = OGAR ogar-from-rails)
- pub mod ar_shape from lib.rs

Kept:
- docs/OGAR_AR_SHAPE_ENDGAME.md §2 corrections (legitimate doctrine,
  partly already in lance-graph from #546)
- E-OGAR-AR-SHAPE-SMOKE-1..6 (findings preserved; code-home corrected
  by the new E-OGAR-AR-SHAPE-REHOME entry)

Net-new for OGAR (NOT re-homed here): the 5 SMOKE-5 commerce concepts
(SalesOrder/SalesOrderLine/FulfillmentFlow/InventoryMovement/
ProductOffering, 0x0207-0x020B) + Spree as a curator. OGAR's commerce
block stops at 0x0206. Those belong in OGAR's full-Class pattern +
ogar-from-rails Spree curator — queued for the OGAR lane, not built
here as lexical detectors.

crate compiles clean without ar_shape (5 pre-existing oxrdf::Subject
deprecation warnings unchanged). #552 closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
AdaWorldAPI pushed a commit that referenced this pull request Jun 21, 2026
…ogar-vocab; record the construction error

Operator flagged #552: ar_shape is generic-AR detection that belongs in
OGAR (the generic AR compiler), not lance-graph-ontology. Verified
against OGAR@156c016: ogar-vocab (3383 LOC) already IS the canonical
registry, richer:

  ar_shape (reverted)            ogar-vocab (canonical)
  CanonicalConcept + codebook_id canonical_concept_id / const CODEBOOK
  concept_of_token               canonical_concept (richer alias arms)
  synergy_registry_one_shot      wire_synergies + Synergy/SynergyMember
  concept_edges                  Association / AssociationKind
  Domain (drafted+reverted)      ConceptDomain + canonical_concept_domain
  6 commerce concepts 0x0201-06  IDENTICAL ids, OGAR PR #64, full Class

ogar-from-rails comment confirms the intended split: "the Odoo-side
extraction lives in the parallel session" — OGAR owns the registry,
lance-graph does the Odoo extraction, they MEET at canonical_concept_id.
I re-implemented the registry instead of consuming it — the same
parallel-object-model construction error the operator called out at the
start of this arc (E-ODOO-CORE-FIRST-STRUCTURAL).

Removed:
- crates/lance-graph-ontology/src/ar_shape.rs (2568 LOC duplicate)
- tests/fixtures/{osb,spree}_ruby_spo.ndjson (9149 LOC; belong where
  the Rails harvest runs = OGAR ogar-from-rails)
- pub mod ar_shape from lib.rs

Kept:
- docs/OGAR_AR_SHAPE_ENDGAME.md §2 corrections (legitimate doctrine,
  partly already in lance-graph from #546)
- E-OGAR-AR-SHAPE-SMOKE-1..6 (findings preserved; code-home corrected
  by the new E-OGAR-AR-SHAPE-REHOME entry)

Net-new for OGAR (NOT re-homed here): the 5 SMOKE-5 commerce concepts
(SalesOrder/SalesOrderLine/FulfillmentFlow/InventoryMovement/
ProductOffering, 0x0207-0x020B) + Spree as a curator. OGAR's commerce
block stops at 0x0206. Those belong in OGAR's full-Class pattern +
ogar-from-rails Spree curator — queued for the OGAR lane, not built
here as lexical detectors.

crate compiles clean without ar_shape (5 pre-existing oxrdf::Subject
deprecation warnings unchanged). #552 closed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants