Problem
Confirmed on current main at 6dc51fa47a9658d26a3f6e51e8a8261aaf9b4b86.
The advisory binder converts an unknown entity label directly from the runtime catalog into TypeId at crates/graphforge-ir/src/binder.rs:4475. Runtime entity IDs start at zero in crates/graphforge-ir/src/catalog.rs:134-160, while ontology type IDs occupy the same raw numeric domain in crates/graphforge-ontology/src/handle.rs:68-77. Relationship runtime IDs are tagged; entity runtime IDs are not.
The first advisory runtime label can therefore alias ontology type 0. A query or write for one label can read or create nodes classified as an unrelated ontology type. This violates the catalog-ID != ontology-ID invariant and can silently corrupt graph semantics.
Objective
Give runtime entity labels and ontology entity types disjoint, durable identities across binding, storage, publication, reopen, and recovery.
Requirements
- Introduce one authoritative conversion/namespace for runtime entity label IDs, analogous to the existing runtime relationship type conversion.
- Remove raw
TypeId(catalog.intern_label(...).0) construction from the binder and any equivalent path.
- Preserve deterministic identity across publication and reopen.
- Define safe handling for existing persisted graphs whose advisory entity values may use the colliding representation: migrate/detect them deterministically or fail explicitly; never silently reinterpret them.
- Keep Rust authoritative. Python and Node remain thin adapters.
- Do not change ontology ID meaning or substitute runtime catalog IDs for ontology IDs.
Acceptance Criteria
BDD Completion Scenarios
Scenario: Advisory and ontology labels remain distinct
Given an ontology whose first entity type is Person
And an advisory query introduces the first runtime label Ghost
When nodes are created and matched through each label
Then each query returns only nodes carrying that label
And the distinction survives publication and reopen.
Scenario: Legacy collision state fails safely
Given persisted state written with a potentially colliding advisory entity representation
When the fixed engine opens or migrates the project
Then it preserves the intended identities or returns an explicit recovery/migration error
And never silently treats one label as another.
Implementation Notes
Likely surfaces: crates/graphforge-ir/src/catalog.rs, crates/graphforge-ir/src/binder.rs, crates/graphforge-storage/src/catalog.rs, ontology/runtime catalog persistence, and facade recovery tests. Audit every raw conversion between RuntimeLabelId, ontology TypeId, and stored node type columns.
Observability
Migration or invalid-state diagnostics may report aggregate counts and identity-domain metadata, but must not log graph properties, UUIDs, or local paths.
Security And Privacy
No new network surface. Treat identity confusion as a data-integrity defect; recovery must fail closed rather than guess.
Testing
Unit-test namespace boundaries and overflow/reserved-domain handling. Add direct bind/create/query/reopen/recovery regressions with ontology ID 0 and the first advisory label. Use deterministic fixtures; no skips, sleeps, retries, or weakened assertions. Run targeted checks and exact-head CI.
Documentation
Update runtime catalog/ontology identity architecture documentation if the encoded representation or compatibility contract changes.
Non-Goals
- Merging runtime catalog IDs with ontology IDs.
- Renumbering ontology types without an explicit compatibility design.
- Moving entity classification into Python or Node.
Related Issues
- Parent tracker will be attached through GitHub's native sub-issue relationship.
Problem
Confirmed on current
mainat6dc51fa47a9658d26a3f6e51e8a8261aaf9b4b86.The advisory binder converts an unknown entity label directly from the runtime catalog into
TypeIdatcrates/graphforge-ir/src/binder.rs:4475. Runtime entity IDs start at zero incrates/graphforge-ir/src/catalog.rs:134-160, while ontology type IDs occupy the same raw numeric domain incrates/graphforge-ontology/src/handle.rs:68-77. Relationship runtime IDs are tagged; entity runtime IDs are not.The first advisory runtime label can therefore alias ontology type 0. A query or write for one label can read or create nodes classified as an unrelated ontology type. This violates the catalog-ID != ontology-ID invariant and can silently corrupt graph semantics.
Objective
Give runtime entity labels and ontology entity types disjoint, durable identities across binding, storage, publication, reopen, and recovery.
Requirements
TypeId(catalog.intern_label(...).0)construction from the binder and any equivalent path.Acceptance Criteria
Personassigned ID 0, the first advisory labelGhosthas a distinct stored and bound identity.MATCH (n:Ghost)cannot returnPersonnodes solely because their raw numeric IDs coincide.PersonandGhostnodes preserves independent membership before and after reopen.BDD Completion Scenarios
Scenario: Advisory and ontology labels remain distinct
Given an ontology whose first entity type is
PersonAnd an advisory query introduces the first runtime label
GhostWhen nodes are created and matched through each label
Then each query returns only nodes carrying that label
And the distinction survives publication and reopen.
Scenario: Legacy collision state fails safely
Given persisted state written with a potentially colliding advisory entity representation
When the fixed engine opens or migrates the project
Then it preserves the intended identities or returns an explicit recovery/migration error
And never silently treats one label as another.
Implementation Notes
Likely surfaces:
crates/graphforge-ir/src/catalog.rs,crates/graphforge-ir/src/binder.rs,crates/graphforge-storage/src/catalog.rs, ontology/runtime catalog persistence, and facade recovery tests. Audit every raw conversion betweenRuntimeLabelId, ontologyTypeId, and stored node type columns.Observability
Migration or invalid-state diagnostics may report aggregate counts and identity-domain metadata, but must not log graph properties, UUIDs, or local paths.
Security And Privacy
No new network surface. Treat identity confusion as a data-integrity defect; recovery must fail closed rather than guess.
Testing
Unit-test namespace boundaries and overflow/reserved-domain handling. Add direct bind/create/query/reopen/recovery regressions with ontology ID 0 and the first advisory label. Use deterministic fixtures; no skips, sleeps, retries, or weakened assertions. Run targeted checks and exact-head CI.
Documentation
Update runtime catalog/ontology identity architecture documentation if the encoded representation or compatibility contract changes.
Non-Goals
Related Issues