Repository navigation
Fixes #35987: Offer registry relation types with their inverses on native ontologies - #35995
Conversation
First unit of #35983. The enum carries the Any or All reviewer rule that later commits put on the ontology, freeze into the review target, and turn into the review task's approval threshold.
…ionship types Decision record for the native relation picker: registry types become native object properties matched by exact predicate IRI, inverses stay reciprocal on write, and the create option is gated on the relationshipType permission.
✅ PR checks passedThe linked issue has a description and all required Shipping project fields set. Thanks! |
Adds the fields to the concept and property schemas, the create requests, the release payload allowlist, and the RDF catalog vocabulary. Adds the DEPRECATED change type, the REPLACED_BY reference role, and the concept-grouped rows of the working diff. Regenerates the TypeScript types.
The IT states the contract before the implementation: setting, creating with, repointing, clearing, deleting and restoring an object property's inverse keeps both sides reciprocal; a one-directional import normalizes on apply and is reused by exact IRI; the release export carries owl:inverseOf both ways.
Concepts and properties now carry deprecation and replacement, and
concepts carry abstract and a definition source:
- A replacement must name another definition of the same kind in the
same ontology. It is indexed as a REPLACED_BY reference, so deleting
a replacement target is refused like any other dangling reference.
- An enumeration needs at least two allowed values. One-value
enumerations stored before this rule stay editable until their values
change.
- Release projection writes om:abstract, dct:source, owl:deprecated,
and dct:isReplacedBy, and native import reads them back.
- Release and working diffs classify a member whose deprecated flag
turned true as DEPRECATED.
- GET /ontologies/{id}/working/diff?groupBy=concept returns the working
changes grouped by the concept they define, with parent labels and a
property count, paged by group under its own revision-bound cursor.
…sion IndependentOntologyClassModelIT covers the field round trips, the enumeration minimum and its legacy carve-out, replacedBy integrity, the DEPRECATED diff row, and grouped diff paging with the 412 cursor rule. NativeOntologyMemoryIT adds a class-create change set and its rollback. Existing ITs that stored one-value enumerations now use two values.
Adds the fields to the concept and property schemas, the create requests, the release payload allowlist, and the RDF catalog vocabulary. Adds the DEPRECATED change type, the REPLACED_BY reference role, and the concept-grouped rows of the working diff. Regenerates the TypeScript types.
Concepts and properties now carry deprecation and replacement, and
concepts carry abstract and a definition source:
- A replacement must name another definition of the same kind in the
same ontology. It is indexed as a REPLACED_BY reference, so deleting
a replacement target is refused like any other dangling reference.
- An enumeration needs at least two allowed values. One-value
enumerations stored before this rule stay editable until their values
change.
- Release projection writes om:abstract, dct:source, owl:deprecated,
and dct:isReplacedBy, and native import reads them back.
- Release and working diffs classify a member whose deprecated flag
turned true as DEPRECATED.
- GET /ontologies/{id}/working/diff?groupBy=concept returns the working
changes grouped by the concept they define, with parent labels and a
property count, paged by group under its own revision-bound cursor.
…sion IndependentOntologyClassModelIT covers the field round trips, the enumeration minimum and its legacy carve-out, replacedBy integrity, the DEPRECATED diff row, and grouped diff paging with the 412 cursor rule. NativeOntologyMemoryIT adds a class-create change set and its rollback. Existing ITs that stored one-value enumerations now use two values.
…d per-operation revisions
…tatus and release change summary
…tatus and release change summary
OntologyWorkingState runs registered finalizers at the outermost mutation's exit, before validation. OntologyPropertyRepository registers one that settles inverse links over the affected neighbourhood: the side that states a link decides it, the latest statement wins, and a partner no longer pointed back at is released. Partners are written through the updater so they version, index and validate like any edit. The RDF importer reads owl:inverseOf as symmetric, so a one-directional file describes both sides and a re-import changes nothing. IndependentOntologyAxiomIT drops its indirect inverse variant, which relied on one-sided inverse chains.
An ontology carries an approvalRule, AnyReviewer by default or
AllReviewers. prepareReview freezes the rule into the task's review
target and leaves the requester's and the author's own user entries out
of the assignees. The rule reaches the workflow as the start variable
approvalMode=allAssignees, which CreateTask turns into the task's
approvalThreshold, so the shared counters stay unchanged.
Each counted vote is recorded on the task in reviewVotes with its
comment; the deciding vote is recorded in the decision transaction with
any voter whose record was lost. Reviewers and the approval rule are
locked with 409 while a release of the ontology is in review.
GET /v1/ontologyReleases/{id}/review and
GET /v1/ontologies/reviews?view=assigned|created|pending read a review
from the task's recorded votes.
Amends ADR:2026-10-14-ontology-reviews-bind-workflow-tasks-to-revisions
as v13.
The list loads the releases of all listed tasks in one batched read. The list test now asserts that exactly one statement reads release rows and that it covers both releases, so a read per task fails it. Resolving each task's about reads only the names of its release, so the assertion does not count that lookup.
Adds a gray badge for deprecated changes to main's AI-mode change list, as StatusBadge.constant.ts colours a deprecated status.
An incoming relation whose property has an inverse now reads the inverse label in both the classic and the AI panel, because the label logic moved into main's shared renderRelationRow. The relation type dialog renders in both modes, so a new relation type can be created from AI mode too. A new AI-mode test covers the inverse label.
Main moved 26 commits before PR1 landed. The merge had no conflicts.
PR1 landed on main as 5f2b733. This branch already holds PR1's final code, so the merge keeps the tree.
The type dialog registers the inverse, then the primary, then patches the inverse to name the primary. These cases fail one of the later writes once and retry the same draft through the dialog. A retry after a failed primary write must reuse the inverse already registered, and a retry after a failed back link must patch the inverse to name the primary. Either way the registry ends with one reciprocal pair and no suffixed duplicate.
The type dialog registers the inverse, then the primary, then patches the inverse to name the primary. When the primary write failed, a retry registered a second inverse with a numeric suffix and left the first one unlinked. When the back link failed, a retry found the primary and returned it, so the pair stayed one-way. The view now remembers the inverse it registered until the back link is written. On a retry, the planner names the draft's inverse after that type and reuses it, or, when the primary is already registered and names it, the view patches the back link. No other type is patched, and the error from the failed write is still shown. A rollback would need the Delete right on relationship types, which the dialog does not check, and the registry's soft delete keeps the name taken, so the retry's create would conflict.
A new case puts another author's unlinked "approves" in the registry and creates "approved by" with the inverse "approves". The view registers "approves2" and links only that type back, so it never claims a type it did not register. The case where the back link failed now also checks that the retry writes the relation once and that only the first failure is reported.
After the back link of "approved by" fails, the author retries with the label "signed off by" and the same inverse "approves". The new case expects the retry to link "approves" back to "approvedBy" and to give "signedOffBy" its own inverse, "approves2", so no type is the inverse of two primaries. It fails until the view finishes the earlier pair before it plans the retry.
When the back link fails, the primary names the inverse but the inverse names nothing. A retry under a new label reused that inverse for the new primary, so the inverse ended up named by two primaries. Before it plans a retry, the view now links the inverse it registered back to the type that already names it, whatever the retry is called, and then plans the draft with no reuse, so the new primary gets its own inverse. The inverse is reused only while nothing names it, after a failed primary write. Only the inverse this view registered is patched.
Two cases in OntologyGraphView.test.tsx pin behavior the view already has. A retry whose back-link repair fails keeps the remembered inverse, so a later retry links it back. A retry drawn from the other end, under the inverse's own label, still links the earlier pair back.
After the back link of "approved by" fails, the author draws the relation from the other end and types "approves". The case already checks that the retry links the earlier pair back. It now also checks the relation the retry writes: the property "approvedBy" first, then "approves" naming it as its inverse, and one axiom on "approves". That is what a first draw of "approves" writes. It fails until the view looks the label up in the registry as the repair left it, because the retry still writes "approves" with no inverse.
When the back link of "approved by" fails and the author retries by drawing "approves", the view links the pair back in the registry. It then looked the label up in the list it loaded before that repair, where "approves" names no inverse. So the retry wrote the property "approves" with no inverse and never declared "approvedBy". A first draw of "approves" writes both properties, linked. finishUnlinkedPair now returns the registry with the patched inverse in place of the stale entry, and the view looks the label up and plans the draft in that list. The retry now writes "approvedBy", then "approves" naming it, then one axiom, as a first draw does. The view still patches only the inverse it registered, keeps it when the repair fails, and reports the failed write once.
Main moved to daec19a before PR3 landed. The merge had no conflicts.
Code Review ✅ Approved 6 closed / 6 findings🔴 High risk · Reciprocal inverse-link writes across ontology edits, imports, deletion, and restoration could leave stored property relationships inconsistent or remove links unexpectedly. Native ontology authors can now pick shared registry relationship types and their inverses, with inverse links kept reciprocal through edits, imports, deletion and restoration. Six issues were resolved this session, including one-sided inverse pairs, reuse of soft-deleted IRIs, restore dropping dangling inverses, review-list task loading, and orphaned registry types on failed pair creation. No open findings remain. ✅ 6 closed✅ Edge Case: Reciprocal-inverse rule skips retargeting, so pairs can stay one-sided
✅ Edge Case: Reuse-by-IRI ignores soft-deleted properties that still hold the IRI
✅ Quality: SETTLE_PASSES=8 finalizer budget is an unrecorded judgment call
✅ Quality: Restore silently drops a dangling inverse; the ADR says it is refused
✅ Performance: listReviews loads a task for every open review on each page request
...and 1 more closed from earlier reviews VerificationRules No rules evaluated Functional validation Not enabledAuto-approval Not enabledFor more verification details, comment OptionsDisplay: compact → Showing compact verification results. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|



Fixes #35987
Native ontology authors can select registry relationship types and their inverses. Native inverse links remain reciprocal through edits, imports, deletion and restoration, while the picker follows bounded registry pagination.
The published stack targets release 2.1.0. Each linked Shipping issue is In Progress with Product, P1 and Governance metadata.
PR1 #35990, PR2 #35994 and PR3 #35997 are merged, and this PR now targets
main. Two chains start at PR4:PR9, PR11, PR13, PR14 and PR16 are not published yet.
Current assessment for
8f4748f4a5f, recorded 2026-10-29.Verdict. PASS. Gitar flagged that a failed registry write could leave an orphan or one-way inverse type. One round and three fix-forwards answered it with eight commits,
56bf99b12e1to09b4142bcec, and an independent review checked each round. The last review passed09b4142bcec, with no blocker or major left open from the four reviews.8f4748f4a5fthen merges PR3's landed head9021493db4aandmain'sdaec19a5ab6, and records PR3's squash5575a2d4d5ewith anoursmerge. Neither merge had a conflict. The diff againstmainhas the same patch-id as the reviewed diff against PR3's headfca4419074b. Earlier reviews passed the merge withmain's AI-mode restyle at4868c52ace0andcf429f94388.Retrying a failed type creation. When a registry write fails while an author creates a relationship type pair, the next try now finishes the pair. The view remembers the inverse type it registered until the back link is written. On a retry it reloads the registry. It links that inverse back to the type that names it, whatever the retry is called, or reuses it when the primary write failed. It then writes the relation the author drew, once. A retry never patches or reuses a type this view did not register. The error from the failed write still shows once.
Known limits. None of these blocks the merge.
OntologyConceptInspector.test.tsxrepeats PR4'spartOfandhasPartfixtures with other IRIs.EntityNotFoundExceptionand stores nothing when its stored inverse partner is live. Restoring the ontology works. That gap needs a design call and an issue, which is not filed yet.Test evidence. On
09b4142bcec, TypeScript reports 0 errors and Jest passes 82 of 82 in PR4's 5 UI test files. ESLint reports 0 errors and the one knownreview-sequential-api-callswarning, Prettier is clean, the Playwright impact map is current, and CI's lint sequence makes no change. The round changed 3 UI files and no Java. The Java gates ran on the earlier merge tree21999f30ecd. Package andspotlesspassed, and 465 unit tests in 26 classes passed. The 7 database classes ran on local MySQL 9.3 and PostgreSQL 14, one new database per run. All passed apart from two invocations ofOntologyEntityDAOTest.reviewLocksSeeEditsAndArchivalCommittedWhileWaiting, whose rows share one database on the local engines. The Java impact map check passed at8f4748f4a5f.CI. Checks at
8f4748f4a5fwere still running when this block was written. Atcf429f94388, every check passed exceptmaven-collate-ci, which failed with HTTP 422 because the Collate repository has no branch named after this PR's old base. This PR now targetsmain.Still owed. None of these has run yet. Run each IT on
-Pmysql-elasticsearchand then-Ppostgres-opensearch, and confirm aTests run:line.IndependentOntologyInverseITandIndependentOntologyAxiomIT.TaskResourceITand the task ITs listed on PR1, which this head carries.IndependentOntologyApprovalIT,IndependentOntologyPublicationITandIndependentOntologyReleaseIT, andWorkflowDefinitionResourceITin the global-state lane.openmetadata-serviceunit suite and every merge-queue IT the planner selects againstmain, with the cache-tests and postgres-opensearch lanes.OntologyNativeRelations.spec.tsandOntologyNativeStudio.spec.tsagainst a live stack. They drive classic mode.OntologyEntityDAOTest.reviewLocksSeeEditsAndArchivalCommittedWhileWaitingunder Testcontainers.AI mode.
main's Ontology Studio AI-mode restyle #36433 now gates new screens on AI mode. AI mode lacks none of PR4's controls. The inverse label, the relation type dialog, the registry-backed picker with its create option and the permission-denied toast all render in code both modes share.Not verified yet. This PR is verified only when its unit, live and performance boxes in the plan are all checked. The operator lands the stack.
Earlier description and verification records
These records preserve the earlier design notes, test results and attachments. Earlier statements about the current head or readiness apply to their recorded revisions. The integration assessment above supersedes those statements.
Fixes #35987
Describe your changes
Native Ontology Studio now offers shared registry relationship types alongside the ontology's own object properties. Selecting Part Of reuses or creates native properties by exact predicate IRI, stores a restriction on the source concept, and shows Has Part on the target's incoming relation. Subclass Of keeps its native hierarchy behavior; annotation properties stay out of the picker.
Inverse links remain reciprocal through edits, imports, deletion, and restoration. New inverse claims release displaced partners. Restoring a self-inverse property preserves its link; restoring a missing partner drops the stale link. Both soft and hard deletion attribute partner cleanup to the deleting user inside the retained transaction. Registry types without an inverse create one native property without inventing a partner.
The picker loads all registry pages within a documented bound, shows inverse labels across page boundaries, and rejects invalid pagination instead of displaying an incomplete catalog. Administrators can create custom registry pairs in the dialog; authors without relationship-type Create permission can use existing types.
Design: ADR:2026-10-21-native-relations-instantiate-registry-relationship-types.
Validation
Current source snapshot:
213ae3115fdf740d405d7cecd338a6f343743c52.ba6d06c88bd931b5854bfe003a288be3c9dee255with identical dependencies, generated models, and external fixtures. Both compilations exit 2 with the same 562 diagnostics; normalized full messages and multiplicity have zero additions or removals.Remaining verification
This is a published implementation snapshot; the following gates remain open under the shared test coordinator:
Type of change
Checklist
REVIEW.mdSummary by Gitar
approvalRulewhile a release is in reviewpublicationStatusfrom active and latest releasesabstract,definitionSource,deprecated, andreplacedByfieldsenumValuesand supportreplacedByThis will update automatically on new commits.


Confidence Score: 5/5
The PR appears safe to merge as part of the reviewed stack, with the fresh-registry permission concern addressed by PR #35991.Summary
Native ontologies now offer registry relationship types and their inverse labels alongside their own object properties.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Choose a relationship type] --> B[Plan native writes] B --> C[Create missing properties or link existing ones] C --> D[Server settles reciprocal inverse links] D --> E[Validate final changes] E --> F[Save source restriction] F --> G[Refresh ontology model] G --> H[Show inverse label on incoming relation]Reviews (21) · Last reviewed commit: "Merge class model fixes into native rela..." · Reviewed by Greptile