diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 93437a27..411c85a3 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,6 +1,18 @@ +## 2026-06-09 — D-IDENTITY Phase B: global entity_type ratified + mint trace correction + +**Main thread (Opus→Fable mid-session).** Decision-gate ratified `entity_type` = GLOBAL shared template id (DECISION-3). Pre-change trace overturned two beliefs: (a) `namespace.rs:12` "dense within the namespace" is STALE — live mint `registry.rs:476` is already global append-order; (b) registry is NOT template-deduped (own claim, corrected in-place in the plan). Blast radius of global/sparse ids traced benign (~16 readers, none dense-index). Synthesis: bijection IS the dedup — one `NiblePath ↔ entity_type` pair table = template registry + dedup index + bijection witness. Plan: DECISION-3 + CORRECTION + refinement. Epiphany: E-MINT-TRACE-1; E-OGAR-NORTHSTAR-1 Status updated. Rides in #481. Next: implement first brick (pair-table mint + round-trip test) in lance-graph-ontology. + +## 2026-06-09 — D-IDENTITY decisions: OGAR mirror (ratified) + north-star template model + +**Main thread (Opus).** Recorded two architecture decisions for the identity arc (no code; plan + epiphany): (1) ontology cache = OGAR one-way OGIT mirror, append-only immutable ClassIds (ratified via decision-gate) — ownership, not drift-prevention; (2) ClassId space organized as a shared north-star template spine (`entity_type`/`NiblePath` = DOLCE-rooted shape reused across domains; `namespace` = domain), realized by the existing octet split + FieldMask inherit/delta + NiblePath ancestry. Plan: identity-architecture DECISION-2 + north-star guard + Phase B refinement. Epiphany: E-OGAR-NORTHSTAR-1. Rides in PR #481. + +## 2026-06-09 — D-IDENTITY-1 review-fix (#480 CodeRabbit) + CI badges + +**Follow-up PR** off merged `main`. Addressed CodeRabbit #480: `from_packed` edge-case test (depth>MAX, high-bit reject, `(0,0)` sentinel, MAX_DEPTH `>>64`-guard boundary, `packed∘from_packed` identity); stale "open DECISION" line → RESOLVED; AGENT_LOG SHA (`947c1e4`); MD040/MD058 in the two plan docs. **Skipped** MD028 (LATEST_STATE) — the blank-between-entries IS the append-only style. Added the **no-content-drift-for-existing** invariant to the plan (sole drift surface = ontology cache not mapped from its authoritative source). Native CI badges (rust-test/style/build) → README. 600 contract lib tests (+1), clippy/fmt clean. + ## 2026-06-09 — D-IDENTITY-1 (Phase A) + 2 cross-repo sweeps — identity-architecture -**Orchestrator:** Opus main thread (autoattended). **Outcome:** Shipped Phase A. +**Orchestrator:** Opus main thread (autoattended). **Outcome:** Shipped Phase A — commit `947c1e4`, PR #480 (merged `62bca5e`). - **Sweep A** (Opus general-purpose): lance-graph + ndarray identity-type inventory → the 128-bit identity space is EMPTY (only `[u8;16]` is `atoms::I4x32`, a style vector); every GUID field already exists as a committed scalar → compose-don't-reinvent. - **Sweep B** (Opus general-purpose): MedCare-rs + smb-office-rs store keys → `EntityKey(&[u8])` already carries any-length keys (smb-bridge `key_to_filter` length-branches on Mongo+Lance); transport solved. MedCare needs one `external_ref` (or reuse DMS `sha256`); smb maps directly. - **Phase A:** `lance_graph_contract::identity::NodeGuid` (UUIDv8, composed from SchemaPtr⊕NiblePath⊕StructuralSignature⊕local) + `NiblePath::from_packed`. 599 contract lib tests (+15), clippy `-D` clean, fmt clean. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 66c76b7d..b0a27d48 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,55 @@ +## 2026-06-09 — E-MINT-TRACE-1 — the live mint is already global (registry.rs:476); the "namespace-local" doc is stale; dedup is net-new; the bijection IS the dedup + +**Status:** FINDING (traced, ratified: `entity_type` = global shared template id) +**Confidence:** High (read the mint, not the doc comment) + +**Trace before change paid twice.** (1) `namespace.rs:12` documents `entity_type_id` as "dense **within the namespace**" — but the actual mint is `registry.rs:476 entity_type_id = (rows.len()+1)`: **global append-order across all namespaces**. The doc comment is stale; the GLOBAL semantics DECISION-2/3 want are already the live behavior. (2) It corrected this session's own claim, minutes old: the registry is **not** template-deduped — every append mints a fresh id (`enumerate_first_with_entity_type_id` is defensive, not reuse evidence). Frugal dedup + the `entity_type↔NiblePath` pairing are net-new. + +**Blast radius traced benign:** ~16 `entity_type_id()` readers store-as-column-value or compare; none dense-index an array BY entity_type. Global/sparse ids break nothing. Dedup consequence: per-id row lookup becomes namespace-ambiguous ⇒ resolve by `(namespace, entity_type)`. + +**The synthesis that shrinks Phase B:** the bijection IS the dedup. One pair table `NiblePath ↔ entity_type` in the registry: path present ⇒ reuse the template id (new row, new namespace); absent ⇒ mint fresh (monotone, never reused) + record the pair. The pair table is simultaneously the template registry, the dedup index, and the bijection witness the round-trip test proves. Moves 1+2 of the Phase B seam are one mechanism. + +**Process lesson (generalizes):** doc comments describe intent at write-time; the mint line is the contract. For any "is this id local or global / dense or sparse" question, read the assignment site and grep for dense-indexing consumers before believing prose. + +**Cross-ref:** identity-architecture plan DECISION-3 + Phase B grounded seam (CORRECTION block); E-OGAR-NORTHSTAR-1 (Status updated); I-LEGACY-API-FEATURE-GATED (the positional `contract/ontology.rs:85` helper is the v1 path to gate). + +## 2026-06-09 — E-ANCESTRY-TRINITY-1 — NiblePath::is_ancestor_of is ONE bit-shift read three ways: subClassOf = supervision-edge = north-star template specialization + +**Status:** FINDING (cross-session convergence — OGAR/SurrealDB session + identity-contract session, independently) +**Confidence:** High + +**The convergence.** A parallel CCA2A session (OGAR / nexgen op-surreal-ast / SurrealDB RecordId) pulled #480 and independently re-derived the OGAR↔lance-graph membrane as **"the registry mint of `(entity_type, NiblePath)` per class"** — exactly DECISION-2 (OGAR mirror) committed from this side in #481. Two sessions, opposite directions, same membrane. + +**The new synthesis it surfaces:** `NiblePath::is_ancestor_of` (a single HHTL bit-shift on the GUID routing prefix) is simultaneously THREE relations: +- **OWL `subClassOf`** (ontology inheritance) — OGAR-AST-CONTRACT §1. +- **OTP supervision edge** (ractor parent-routing / delegation through `OrchestrationBridge`) — the other session's "supervisor-edge is now [G] mechanical" finding. +- **North-star template specialization** (a domain class descends from its shared template) — E-OGAR-NORTHSTAR-1. + +They are the SAME relation: the north-star template hierarchy IS the routing/supervision hierarchy IS the subClass hierarchy — one bit-shift, three names. Consequence: reusing a template (inherit + switch namespace), being-supervised-by, and being-a-subclass-of are the same arithmetic; there is no separate routing structure to maintain. + +**Coordination:** the OGAR session is on #480 (Phase A); #481 carries the OGAR-side answer it needs — OGAR = OGIT mirror, immutable ClassIds, north-star spine, `namespace`=domain. Its proposed `D-IDENT` paired-note + `D-IDENTITY-PIN` should absorb the `namespace`=domain + north-star framing on next pull. + +**Cross-ref:** E-OGAR-NORTHSTAR-1; E-IDENTITY-WHITEBOX-1; identity-architecture DECISION-2 + north-star guard; `hhtl.rs::is_ancestor_of`. + +## 2026-06-09 — E-OGAR-NORTHSTAR-1 — ontology cache = OGAR mirror with a reusable north-star template spine (namespace specializes, entity_type is shared) + +**Status:** DECISION (OGAR mirror RATIFIED via decision-gate; north-star template model RATIFIED 2026-06-09 "frugal it is"; `entity_type` = GLOBAL shared template id RATIFIED via decision-gate — see E-MINT-TRACE-1) +**Confidence:** High (both halves ratified; mint trace confirms global append-order is already the live mint) + +**Two decisions, one architecture.** + +(1) **OGAR mirror (ratified).** The ontology cache's source of truth is OGAR — a one-way mirror of OGIT (+ OWL / Wikidata class-backbone / HHTL) with an append-only immutable ClassId space (protobuf-field-number discipline: mint once, never renumber, tombstone deprecations). Chosen for OWNERSHIP + dissolving the upstream dependency — and, pre-production, immutable ClassIds upgrade NodeGuid from "stable within an OGIT version" to "stable forever." Explicitly NOT a drift fix: content-drift for existing entities does not exist once the cache is mapped from a source (Stefan's correction, twice). The mirror buys ownership, not drift-immunity. + +(2) **North-star template spine (recommended model).** The ClassId space is NOT a flat domain×shape explosion. `entity_type`/`NiblePath` is a SHARED, DOLCE-rooted SHAPE template (small spine, reused across domains); `namespace:u8` selects the domain (healthcare / Odoo / WoA-rs / OpenProject-nexgen-rs / OWL / Wikidata). A domain reuses a template by default (switch namespace, inherit the field-set), specializes via NiblePath-descent + FieldMask delta, and mints a new ClassId only for a genuinely novel shape. + +**It's the intended reading of the NodeGuid octet split, not new machinery.** `namespace:u8 | entity_type:u16 | kind:u8` already separates domain from shape; `FieldMask + inherit` (parent-OR-delta, class_view.rs) already IS template-reuse-with-delta; `NiblePath::is_ancestor_of` already IS template→specialization ancestry; `dolce_category_id` already roots the spine. The mechanism exists; the curated template ontology + domain→template mappings are the OGAR / Phase B content. + +**Frugality is double:** (a) the ClassId space is shape-sized (templates), not domain×shape-sized — fits u16 with room; (b) the shape codebook / palette / shape_hash is encoded ONCE per template and shared 256 ways, and cross-domain alignment is free (same entity_type ⇒ same shape). Reusable templates compose WITH immutability (they ARE the immutable spine) — frugality and stability reinforce, they don't trade off. Per-domain precision is preserved by the FieldMask delta, so "lazy" here is DRY, not sloppy; the only real cost is curation (the template boundaries), which is OGAR's editorial job. + +**Phase B becomes:** stand up OGAR as the OGIT mirror + north-star template registry; seed entity_type↔NiblePath from it; the build-time round-trip proves the bijection. The surrealdb-coords blocker (N8 / Phase H) is unrelated and remains. + +**Cross-ref:** identity-architecture plan DECISION-2 + the north-star guard; E-IDENTITY-WHITEBOX-1 (NodeGuid composition); I-VSA-IDENTITIES (closed template vocabulary interns; Wikidata's open instance mass stays content, never a ClassId). + ## 2026-06-09 — E-IDENTITY-WHITEBOX-1 — structured identity + round-trip converts the substrate from black-box to CI-falsifiable **Status:** FINDING (Phase A landed: `identity::NodeGuid` composed, 15 tests green) diff --git a/.claude/plans/cognitive-write-roundtrip-substrate-v1.md b/.claude/plans/cognitive-write-roundtrip-substrate-v1.md index 92b9d686..69c72de8 100644 --- a/.claude/plans/cognitive-write-roundtrip-substrate-v1.md +++ b/.claude/plans/cognitive-write-roundtrip-substrate-v1.md @@ -41,7 +41,7 @@ u16 ⊕ `MetaWord` u32) and deliberately EXCLUDES the lossy CAM-PQ fingerprint ### MAP 1 — the round-trip (the whole point) -``` +```text WRITE (project = encode) READ-BACK (decompile = decode) Vec Vec │ intern (s,p,o) → ids (dict) ▲ dict reverse: ids → (s,p,o) @@ -87,7 +87,7 @@ u16 ⊕ `MetaWord` u32) and deliberately EXCLUDES the lossy CAM-PQ fingerprint ### MAP 5 — THINK/DO (Semantik/Pragmatik) both round-trip as triples -``` +```text Class (shape, subClassOf) ──► triples: (subj rdf:type ogit:ObjectType), (field depends_on …) THINK ActionDef (DO, object_class→Class, OdooMethodKind, KausalSpec) ──► triples: (amount_total emitted_by _compute_amount) DO │ │ diff --git a/.claude/plans/identity-architecture-exists-vs-needs-v1.md b/.claude/plans/identity-architecture-exists-vs-needs-v1.md index f5a2f5b7..fad5c140 100644 --- a/.claude/plans/identity-architecture-exists-vs-needs-v1.md +++ b/.claude/plans/identity-architecture-exists-vs-needs-v1.md @@ -47,6 +47,7 @@ against what must be built, and phases the integration. ## WHAT EXISTS — grounded inventory (6 layers, file:line) ### Layer 0 — address / discriminator scalars (the GUID's fields) + | Type | Width | Role | Status | Evidence | |---|---|---|---|---| | `NiblePath{path:u64,depth:u8}` | 72 | HHTL tree address (basin/child/is_ancestor_of, 16ⁿ) | **[G]** | hhtl.rs | @@ -59,6 +60,7 @@ against what must be built, and phases the integration. | `EdgeRef{family:u8,local:u16}` | 24 | episodic HHTL family+local address | **[G]** | episodic_edges.rs:34 | ### Layer 1 — edge / handoff carriers (the LE "sound members") + | Type | Width | Role | Status | |---|---|---|---| | `EpisodicEdges64(u64)` = 4×EdgeRef, MRU promote/evict, `to_le_bytes` | 64 | AriGraph episodic edges | **[G]** episodic_edges.rs | @@ -67,6 +69,7 @@ against what must be built, and phases the integration. | `MailboxId=u32`, `MailboxRow{mailbox_ref:u32,row_idx:u32}` | 32/64 | mailbox + row address | **[G]** | ### Layer 2 — cold-path stores (TODAY: thin + inconsistent) + | Store | Key | Status | |---|---|---| | `MetadataStore`: `NodeRecord{node_id:u32, label:String, properties:HashMap}`, `EdgeRecord{source:u32,target:u32,edge_type:String}` | u32 + **STRING label/props (legacy Cypher)** | **[G]** metadata.rs:60,86 | @@ -75,12 +78,14 @@ against what must be built, and phases the integration. | `WitnessId(u64)` (arigraph witness) | 64 opaque handle | **[G]** witness_corpus.rs:63 | ### Layer 3 — resolution (class-from-address) + | Surface | Status | |---|---| | `RegistryClassView: ClassView` (fields/template/dolce_category_id) | **[G] resolve / [H] field-enum deferred** class_resolver.rs | | `OntologyRegistry`: `resolve_uri`, `enumerate_first_with_entity_type_id(u16)`, `resolve_iri_in` | **[G]** registry.rs | ### Layer 4 — commit + witness (the membrane) + | Surface | Status | |---|---| | `SoaEnvelope` trait + `ColumnDescriptor` (container-LE geometry) | **[G] trait / [H] ZERO impls** soa_envelope.rs | @@ -91,6 +96,7 @@ against what must be built, and phases the integration. | `SlaPolicy`, `TenantScope` | **[G] types** sla.rs | ### Layer 5 — cross-store transport (the consumer boundary) + | Surface | Status | |---|---| | `EntityKey<'a>(pub &'a [u8])` — opaque length-agnostic key | **[G]** repository.rs:12 | @@ -100,6 +106,7 @@ against what must be built, and phases the integration. | smb-office-rs: Mongo `ObjectId`(12B) + `String` refs; actively impls repository | **[G]** base.rs:92 | ### Layer 6 — round-trip / substrate-hardening + | Surface | Status | |---|---| | `TripletProjection` trait + `roundtrip_eq` → `RoundTripFailure` | **[G]** codegen_spine.rs:107 | @@ -168,7 +175,7 @@ content), never VSA-bundled. | Phase | Gap | Crate | Deliverable | DoD | Dep | |---|---|---|---|---|---| | **A** | N1 | contract | `NodeGuid`/`EdgeGuid` as composition of existing fields + layout version | byte-decompose round-trips to `SchemaPtr`/`NiblePath`/`StructuralSignature`/`local`; UUIDv8 validates; zero-dep; clippy/fmt | — | -| **B** | N2 | ontology | wire `StructuralSignature` → `RegistryClassView` (enumerate field-set from `MappingRow`) | `shape_hash(class_id)` returns a stable signature; the deferred D-CLS field-enum closed | A | +| **B** | N2 | ontology (OGAR) | OGAR = one-way OGIT mirror; mint **immutable append-only ClassIds** over a shared **north-star template spine** (DOLCE-rooted shapes reused across domains via `namespace` + `FieldMask` inherit/delta); seed `entity_type ↔ NiblePath` from it; wire `StructuralSignature` → `RegistryClassView` | bijection round-trips at build time; `shape_hash(class_id)` stable; ClassId never renumbers (protobuf-field-number discipline); D-CLS field-enum closed | A | | **C** | N3 | shader-driver | `impl SoaEnvelope for MailboxSoA` (zero-copy) | `as_le_bytes().as_ptr()==backing`; `verify_layout()` green | — | | **D** | N4 | lance-graph | cognitive-write `TripletProjection` + `roundtrip_eq` over the identity graph | passes the `account.move` fixture; corrupt-pack fails; (f,c) within 1/1023 | A, C | | **E** | N5 | callcenter | `project_graph` (node/edge emitter) through `commit_event`+gate | committed cycle queryable as `NodeGuid` nodes + `EdgeGuid` edges; version ticks; RBAC applies | A, D | @@ -179,6 +186,64 @@ content), never VSA-bundled. **Critical path:** A → (B, C) → D → E → F. G hangs off A (parallel). H is gated. **Smallest unblocked first brick:** Phase A (the `NodeGuid` composition, zero-dep contract) OR Phase C (the `SoaEnvelope` impl) — both leaf, both needed by D. +### Phase B — grounded mint seam (2026-06-09, frugal north-star) + +Reading the live surfaces confirmed the frugal model *finishes what's there*, not a rewrite: + +- **Two `entity_type` worlds today.** `contract/ontology.rs:85 entity_type_id()` is a + **1-based positional index** into `Ontology.schemas` (`Customer→1, Invoice→2 …`; test + `entity_type_id_returns_1_based_index`) — **mutable**: insert/reorder a schema and every + id renumbers. The legacy anti-pattern, in our own code. The **`OntologyRegistry`** + (`lance-graph-ontology/registry.rs`) is **append-only** (`RegistryState::append`, + "no rebuild per append") — the correct immutable home and the OGAR-mirror foundation. +- **CORRECTION (traced 2026-06-09 — doc comments are not the mint).** The live mint is + `registry.rs:476 entity_type_id = (self.rows.len()+1)` — **global append-order, + append-only-immutable** (good: the *global* axis DECISION-2 + the bijection want is + ALREADY the mint reality; `namespace.rs:12` "dense within the namespace" is **stale**). + BUT it is **unique per row, NOT template-deduped** — every append gets a fresh id, so + "N rows → 1 entity_type" is *not* today's behavior (`enumerate_first_*` is defensive, not + reuse). ⇒ frugal dedup + the `entity_type↔NiblePath` pairing are **net-new**, not + half-built. Two mints also disagree: `registry.rs:476` (global append) vs + `contract/ontology.rs:85` (per-Ontology positional) — canonicalize on the registry, + gate the helper. +- **Blast radius is benign for the global switch.** The ~16 `entity_type_id()` readers + *store* it as a column value (`bindspace.entity_type[row]`) or *compare* it; **none** + dense-index an array BY `entity_type`. Global/sparse ids break nothing. The one dedup + consequence: `enumerate_first_with_entity_type_id` becomes namespace-ambiguous (multiple + rows per id) ⇒ the resolver wants `(namespace, entity_type) → row`. + +**[DECISION-3] RATIFIED (2026-06-09, decision-gate):** `entity_type` is **GLOBAL** +(the shared north-star template id), NOT namespace-local. The stale +`namespace.rs:12` "dense within the namespace" comment is superseded — and the +traced mint (`registry.rs:476`, global append-order) already agrees. `namespace` +is the domain axis; alignment across domains is a u16 compare. + +**Refinement — the bijection IS the dedup (moves 1+2 are one mechanism):** the +registry keeps a pair table `NiblePath ↔ entity_type`. On append: proposal's +path already in the table ⇒ **reuse** that `entity_type` (new row, new +`namespace`, same template id); path absent ⇒ mint fresh (monotone, never +reused; gaps fine) + record the pair. The pair table is simultaneously the +template registry, the dedup index, and the bijection witness the round-trip +test proves. + +**Four frugal moves (each a landable brick):** +1. **Mint in the append-only registry, deduped by template.** `entity_type` is assigned by + append order (immutable; OGAR seeds it one-way from OGIT → ids frozen, protobuf-field- + number discipline), and a shape mints ONCE — domains reuse it by appending a row with a + new `namespace`, same `entity_type`. That IS the frugality (one shape codebook, 256-way + `namespace` reuse; cross-domain alignment is a u16 compare). +2. **Pair `entity_type ↔ NiblePath` at mint.** The registry mints the bijective pair; + `niblepath_of(entity_type)` stable. +3. **Build-time round-trip test.** `entity_type_of(niblepath_of(et)) == et` both ways over + the seeded spine ⇒ eineindeutigkeit becomes CI-falsifiable (closes DECISION-2 enforcement + (a)+(b)). +4. **Feature-gate the legacy positional `ontology.rs::entity_type_id`** per + I-LEGACY-API-FEATURE-GATED — route through the registry or gate to a documented + non-canonical path; no-renumber / field-isolation test mandatory. + +**First brick:** moves 1+2+3 together (mint + pairing + round-trip); the legacy-gate (4) +follows once nothing canonical reads the positional helper. + ## Honest ledger - **[G] (exists, reuse):** all 6 layers above — `NiblePath`, `SchemaPtr`, `ClassId`, @@ -190,14 +255,50 @@ content), never VSA-bundled. green-field invention. The largest is N6 (cold-path string→identity migration). - **[BLOCKED(C)]:** N8 only (surrealdb fork coords — human gate; lance-graph P0 "STOP and ask"). -- **One open [DECISION]:** D1 vs D2 (SchemaPtr-entity_type vs NiblePath-prefix as - the class carrier) — recommendation: both (exact + routing prefix). +- **[DECISION] RESOLVED (Phase A):** carry BOTH — `entity_type:u16` is the exact + canonical class; the `NiblePath` prefix is the bijective *derived* routing view + (full statement in the "DECISION — RESOLVED" block above). Landed in `NodeGuid`. +- **[DECISION-2] RATIFIED (2026-06-09):** the ontology cache's home is **OGAR**, + a *one-way mirror of OGIT* (+ OWL / Wikidata class-backbone / HHTL) with an + **append-only immutable ClassId** space — chosen for **ownership + dissolving + the upstream dependency**, NOT as a drift fix (drift is already contained by + map-as-source; see the guard below). The ClassId space is organized as a + **shared north-star template spine**: `entity_type`/`NiblePath` is the DOLCE- + rooted *shape* (small, reused across every domain); `namespace:u8` selects the + domain (healthcare / Odoo / WoA-rs / OpenProject-nexgen-rs / OWL / Wikidata). + Domains REUSE a template by default (switch `namespace`, inherit the field-set); + specialize only via `NiblePath` descent + `FieldMask` delta; mint a new ClassId + only for a genuinely novel shape. The surrealdb-coords blocker (N8/Phase H) is + unrelated and remains. ## Guards (iron rules this plan must not violate) - **I-VSA-IDENTITIES:** the GUID is a register key that POINTS TO content; never VSA-bundle it, never intern open content (only the closed vocabulary). Identities intern; scanned papers / free text stay in consumer stores (Layer 5). +- **No content-drift for existing entities — ontology-cache provenance is the ONLY + drift surface.** An existing entity's identity is a *derived function* of its + ontology-mapped class (`entity_type` / `NiblePath` / `shape_hash` all fall out of + the registry mapping), so a mapped entity **cannot** drift from its content. The + one residual drift surface is an **ontology cache that was not mapped from the + authoritative source** (hand-filled / stale / regenerated out-of-band) — that, + not per-instance divergence, is exactly what the `shape_hash` witness reading of + `NodeGuid` guards. ⇒ the registry MUST treat the authoritative ontology as its + mapped source (the Phase B bijection is *seeded from* that source, never from a + hand-filled cache). +- **North-star templates — reuse is the default, mint-new is the exception.** + `entity_type`/`NiblePath` is a *shared* DOLCE-rooted shape spine: the same + template ClassId is reused across domains, disambiguated by `namespace:u8`. + Reuse via `FieldMask` inherit (parent-OR-delta) where a domain shape aligns; + `NiblePath`-descent + delta where it specializes; a new template ClassId ONLY + for a genuinely novel shape. DRY-frugal (the shape codebook / `shape_hash` is + encoded once, reused 256 ways; cross-domain alignment is free — same + `entity_type` ⇒ same shape) AND composes with immutability — reusable templates + ARE the immutable spine, not a relaxation of it. Reuse ≠ drift: sharing a + template across domains is intended, not the cache-provenance drift surface. + NB: the *mechanism* (octet split + inherit/delta + ancestry + `dolce_category_id`) + exists today; the *content* (the curated template ontology + domain→template + mappings) is the OGAR / Phase B build. - **Compose, don't parallel (Agent A #2):** N1 MUST subsume `SchemaPtr` + `EdgeRef`, not re-pack ns/class/family beside them. - **I-LEGACY-API-FEATURE-GATED:** N6's string→identity layout reclaim needs a diff --git a/README.md b/README.md index 7ed086b7..0f9e505e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Lance Graph +[![Rust Tests](https://github.com/AdaWorldAPI/lance-graph/actions/workflows/rust-test.yml/badge.svg)](https://github.com/AdaWorldAPI/lance-graph/actions/workflows/rust-test.yml) [![Style Check](https://github.com/AdaWorldAPI/lance-graph/actions/workflows/style.yml/badge.svg)](https://github.com/AdaWorldAPI/lance-graph/actions/workflows/style.yml) [![Build](https://github.com/AdaWorldAPI/lance-graph/actions/workflows/build.yml/badge.svg)](https://github.com/AdaWorldAPI/lance-graph/actions/workflows/build.yml) + Lance Graph is a Cypher-capable graph query engine built in Rust with Python bindings for building high-performance, scalable, and serverless multimodal knowledge graphs. This repository contains: diff --git a/crates/lance-graph-contract/src/hhtl.rs b/crates/lance-graph-contract/src/hhtl.rs index 95d906ba..84249c40 100644 --- a/crates/lance-graph-contract/src/hhtl.rs +++ b/crates/lance-graph-contract/src/hhtl.rs @@ -322,6 +322,40 @@ mod tests { assert_eq!(NiblePath::EMPTY.basin(), None); } + #[test] + fn from_packed_validates_depth_high_bits_and_roundtrips() { + // (0, 0) is the EMPTY sentinel. + assert_eq!(NiblePath::from_packed(0, 0), Some(NiblePath::EMPTY)); + + // A well-formed (path, depth) reconstructs exactly what `child` builds. + assert_eq!( + NiblePath::from_packed(0x12, 2), + Some(NiblePath::root(0x1).child(0x2)), + ); + + // depth > MAX_DEPTH is rejected. + assert_eq!(NiblePath::from_packed(0, MAX_DEPTH + 1), None); + + // Bits set above the 4·depth route nibbles are an inconsistent pack. + // depth = 2 ⇒ only the low 8 bits may be set; 0x112 has a 9th. + assert_eq!(NiblePath::from_packed(0x112, 2), None); + + // Boundary: at MAX_DEPTH the whole u64 is usable (the `used_bits < 64` + // guard skips a `>> 64` UB), so even all-ones round-trips. + let max = NiblePath::from_packed(u64::MAX, MAX_DEPTH); + assert_eq!(max.map(NiblePath::packed), Some((u64::MAX, MAX_DEPTH))); + + // packed ∘ from_packed is identity on every valid path. + for p in [ + NiblePath::EMPTY, + NiblePath::root(0x3), + NiblePath::root(0x3).child(0x5).child(0xA), + ] { + let (path, depth) = p.packed(); + assert_eq!(NiblePath::from_packed(path, depth), Some(p)); + } + } + #[test] fn depth_caps_at_max_and_rejects_out_of_range_nibble() { // Fill to MAX_DEPTH, then one more child is a no-op (not a wrap/overflow).