Repository navigation
Fixes 36189: Complete the native Ontology Studio shell and scoped queries - #36190
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.
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.
Only an ontology review reconciles the deciding vote, so recording counted votes on any other multi-approval task would always leave its last vote out and add version bumps to glossary approvals. The waiting branch now records a vote only for an ontology review. OntologyReleaseReviewReaderTest reads reviews through the reader with literal tasks and votes. OntologyReleaseRepository.page and offset are static so the cursor math has a test with literal rows.
A restore relinks or drops its inverse through the reciprocity finalizer, which writes the same row a second time inside one mutation. Validation kept the first row per id, so it still saw the dangling inverse and refused the restore. OntologyWorkingState now folds a mutation's changes to one per definition before finalizers and validation see them. A restored property whose partner was hard-deleted meanwhile drops the link before its updater runs, so the restore response matches the stored row, and IndependentOntologyInverseIT covers it on both profiles.
The native relation picker lists the relationship-type registry in two groups, Core and Custom, with each type's inverse beside it, plus the ontology's own object properties that no registry predicate backs. Picking a type writes the missing property pair with the registry predicates as IRIs, linked through inverse, then the restriction axiom. An admin can create a custom type with its inverse from the picker; the dialog writes both registry types and links them before instantiating. The concept inspector labels incoming relations with the inverse. The Playwright spec covers the groups, Part Of, a custom type, and a steward who sees the types but no create option.
…RDF ontology The release inherits its reviewers from the ontology, and the entity updater drops inherited references from a reviewer list, so the task's REVIEWS rows vanished on the workflow's first stage update. OntologyReview.eligibleReviewers now returns plain copies. A counted vote whose record loses every optimistic-locking retry answers 200 with a warning, because the vote already counted in Flowable and the decision records it. openmetadata.ttl declares om:approvalRule, which the ontology projection writes. The ITs unwrap the SDK's wrapped 409 and fetch the task with its reviewers.
…anges count, drop restating comments
The server fills only `id` on a relationship type's `inverse` reference, so the catalog and the picker now resolve a type's inverse by `id`, with `fullyQualifiedName` as the fallback, instead of by a `name` that is never set. Custom types with an inverse show both labels again. When both properties of a pair are already live but the one the type names does not point at the type's partner, the plan patches that property's `inverse` instead of leaving the pair unlinked. The spec covers a second Part Of that reuses the pair and a Subclass Of that writes only the hierarchy axiom.
…sets The binding read on main now returns one page of at most 100 visible assets, and a page can end short with a cursor when hidden rows use up its scan budget. The deprecation impact now follows each after cursor until it is null, then sorts and counts every visible asset. The release resource builds its binding store with the registered asset tables, matching the store's new constructor. Both call sites failed to compile against main. The decision record now describes the paged read.
The merge brings release validation's paged binding read. The deprecation impact now reads every page of a concept's bindings, and the release resource builds its binding store with the registered asset tables. This branch changes none of those files, and the merge had no conflict.
The merge brings release validation's paged binding read through review publish. This branch changes none of its files, and the merge had no conflict.
The merge brings release validation's paged binding read through review tasks. This branch changes none of its files, and the merge had no conflict.
The merge brings library install state (#35989) and the per-server credential cache (#36217). Git merged docs/generated/api-reference.md without a conflict, but both sides had raised the endpoint count from 1988 to 1989, so the merged count was one short. The doc is regenerated, which gives 1990 endpoints and 1980 summaries, and the POST /v1/users/generateToken row keeps main's text. .github/java-tests/impact-map.json merged without a conflict and keeps both sides' entries.
The merge brings release validation's last head, which merged main after library install state (#35989) and the per-server credential cache (#36217). Twenty-four files merged without a conflict: the 21 locale files, rest/ontologyAPI.ts, the generated ontology.ts type and the Playwright impact map. In each, the two sides added separate entries, and the merge keeps both.
The merge brings review publish's update for main after release validation (#36183), library install state (#35989) and the per-server credential cache (#36217) landed. Twenty-three files merged without a conflict: the 21 locale files, rest/ontologyAPI.ts and the Playwright impact map. In each, the two sides added separate entries, and the merge keeps both.
The merge brings review tasks' update for main after release validation (#36183), library install state (#35989) and the per-server credential cache (#36217) landed. Twenty-two files merged without a conflict: the 21 locale files and the Playwright impact map. In each, the two sides added separate entries, and the merge keeps both.
Code Review ✅ Approved 4 closed / 4 findings🟡 Medium risk · The native shell rewires ontology scope, selection, publication, and import/export flows; mistakes could direct users to the wrong ontology or release. Completes the native Ontology Studio shell with scoped tree, lifecycle inspector, version chip and unpublished counts following the active ontology. Four issues were resolved: tree row click no longer snaps back under router transitions, the canonical-template hiding rule is now recorded in docs/decisions, the version chip aria-label no longer hides its visible text, and the Publish count shows loading instead of 'Unavailable' while the ontology list loads. No open findings remain. ✅ 4 closed✅ Bug: Tree row click snaps selection back under router transitions
✅ Quality: Canonical-template hiding rule is not recorded in docs/decisions
✅ Quality: Version chip aria-label hides its visible version text
✅ Edge Case: Publish count reads 'Unavailable' while the ontology list is loading
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 |
|
| Count | Rule |
|---|---|
| 1 | openmetadata-imports/review-sequential-api-calls |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/governance/ontology/OntologyGraphView/OntologyGraphView.tsx:536:9 |
openmetadata-imports/review-sequential-api-calls |
Review these sequential API requests. If they are independent, start them together with Promise.all/Promise.allSettled; keep sequencing only when data-dependent |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed
|
…36190 - OntologyReleaseReviewers: allow an empty collection once text is typed. The search starts on focus, so its results land after a fast fill; React Aria only opens the list on a text change with options present, so the reviewer options never appeared (OntologyReview.spec.ts:348). - OntologyStudioHeader (AI mode): move the scope menu and actions onto the wrapping tab row. PageHeader's actions slot never shrinks, so at 924px beside the Governance panel the Tasks button ran ~95px past the viewport and the subtitle collapsed to one word per line. - OntologyNativeTreeShell.spec.ts: drop the one-row assertion; the AI header puts the mode tabs on their own row by design. - OntologyNativeOperations.spec.ts: since #36187 only a definitive rejection offers Retry review, on ontology-recovery-action; a 5xx only offers Check status. Mock a 422 and use that button.
… Studio Playwright failures (open-metadata#36645) * fix(ui): record Studio Tasks drawer counts in the Classic tour fixtures open-metadata#36188 mounted OntologyTasksDrawer on the Ontology Explorer page; its pending/created count queries run on mount, so the Classic product tour sent them to the server and ProductTour.spec.ts failed on every merge-queue run. * fix(ui): repair the ontology Studio Playwright failures from open-metadata#36187-open-metadata#36190 - OntologyReleaseReviewers: allow an empty collection once text is typed. The search starts on focus, so its results land after a fast fill; React Aria only opens the list on a text change with options present, so the reviewer options never appeared (OntologyReview.spec.ts:348). - OntologyStudioHeader (AI mode): move the scope menu and actions onto the wrapping tab row. PageHeader's actions slot never shrinks, so at 924px beside the Governance panel the Tasks button ran ~95px past the viewport and the subtitle collapsed to one word per line. - OntologyNativeTreeShell.spec.ts: drop the one-row assertion; the AI header puts the mode tabs on their own row by design. - OntologyNativeOperations.spec.ts: since open-metadata#36187 only a definitive rejection offers Retry review, on ontology-recovery-action; a 5xx only offers Check status. Mock a 422 and use that button. * test(playwright): expect the sentence-case Ontology library title open-metadata#35989 renders the library drawer title sentence-case; open-metadata#36190's spec still expected title case. It never ran that far before, because it failed earlier at the 924px header check. * test(playwright): load reviewer options before typing in OntologyReview The reviewer picker searches only once focused, and React Aria opens its list only on a text change while options are already loaded, so filling the whole name at once left the list closed. Click the picker and wait for its first user search before typing. Reverts the allowsEmptyCollection change: it read the previous render's search text, so it never opened the list for a single fill. * test(playwright): note why OntologyReview loads reviewer options first * test(playwright): match the disabled approval-rule select in OntologyReview In a locked review the rule control also renders a focusable tooltip trigger with the same name, so getByRole('button', { name: 'All' }) hit a strict-mode violation. The step never ran before the reviewer picker step was fixed.
…ries (#36190) * WIP: add OntologyApprovalRule schema for release review rules 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. * WIP: record that ontology definition status is derived from releases * Fixes #35987: Record that native relations instantiate registry relationship 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. * WIP: add abstract, source, and deprecation fields to native classes 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. * Update generated TypeScript types and dereferenced schemas * Add IndependentOntologyInverseIT for reciprocal native inverses 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. * Validate, project, and diff the class model fields 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. * Cover the class model fields in integration tests and record the decision 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. * WIP: add abstract, source, and deprecation fields to native classes 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. * Validate, project, and diff the class model fields 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. * Cover the class model fields in integration tests and record the decision 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. * Fix class model integration test assertions for wrapped SDK errors and per-operation revisions * Fixes #35981: Derive concept lifecycle status, ontology publication status and release change summary * WIP: record that ontology definition status is derived from releases * Fixes #35981: Derive concept lifecycle status, ontology publication status and release change summary * WIP: add approvalRule, reviewVotes and the release review read schemas * Keep native inverses reciprocal on write 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. * Decide ontology release reviews by the ontology's approval rule 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. * Record votes only for ontology reviews and test the review read model Only an ontology review reconciles the deciding vote, so recording counted votes on any other multi-approval task would always leave its last vote out and add version bumps to glossary approvals. The waiting branch now records a vote only for an ontology review. OntologyReleaseReviewReaderTest reads reviews through the reader with literal tasks and votes. OntologyReleaseRepository.page and offset are static so the cursor math has a test with literal rows. * Validate the net change of each ontology definition A restore relinks or drops its inverse through the reciprocity finalizer, which writes the same row a second time inside one mutation. Validation kept the first row per id, so it still saw the dangling inverse and refused the restore. OntologyWorkingState now folds a mutation's changes to one per definition before finalizers and validation see them. A restored property whose partner was hard-deleted meanwhile drops the link before its updater runs, so the restore response matches the stored row, and IndependentOntologyInverseIT covers it on both profiles. * Offer registry relation types with their inverses in the native picker The native relation picker lists the relationship-type registry in two groups, Core and Custom, with each type's inverse beside it, plus the ontology's own object properties that no registry predicate backs. Picking a type writes the missing property pair with the registry predicates as IRIs, linked through inverse, then the restriction axiom. An admin can create a custom type with its inverse from the picker; the dialog writes both registry types and links them before instantiating. The concept inspector labels incoming relations with the inverse. The Playwright spec covers the groups, Part Of, a custom type, and a steward who sees the types but no create option. * chore(playwright): auto-refresh impact-map.generated.json * Skip the reference delete for a definition that does not exist yet * Keep the review task's own reviewers and declare approvalRule in the RDF ontology The release inherits its reviewers from the ontology, and the entity updater drops inherited references from a reviewer list, so the task's REVIEWS rows vanished on the workflow's first stage update. OntologyReview.eligibleReviewers now returns plain copies. A counted vote whose record loses every optimistic-locking retry answers 200 with a warning, because the vote already counted in Flowable and the decision records it. openmetadata.ttl declares om:approvalRule, which the ontology projection writes. The ITs unwrap the SDK's wrapped 409 and fetch the task with its reviewers. * Fixes #35981: Cover property and ontology edits in the unpublished-changes count, drop restating comments * Match registry inverses by id and link reused property pairs The server fills only `id` on a relationship type's `inverse` reference, so the catalog and the picker now resolve a type's inverse by `id`, with `fullyQualifiedName` as the fallback, instead of by a `name` that is never set. Custom types with an inverse show both labels again. When both properties of a pair are already live but the one the type names does not point at the type's partner, the plan patches that property's `inverse` instead of leaving the pair unlinked. The spec covers a second Part Of that reuses the pair and a Subclass Of that writes only the hierarchy axiom. * Fix review vote durability and authorized pagination * Keep restored inverses and load every registry page * Attribute inverse cleanup to the deleting user * fix: persist workflow votes with terminal task decisions * Fixes #35981: Clarify the derived publication status contract * Fixes #35987: Map native ontology changes to browser coverage * test: assert ontology review approval rule round trips * Fix inverse deletion actor test email length * test(ontology): cover property validation and grouped diff cursors * test(ontology): reject release headers as captured definitions * feat(ontology): validate captured releases with native SHACL * Use the registered fixture actor for inverse rollback tests * feat: review and publish native ontology releases in Studio * feat: review ontology releases from the Studio Tasks drawer * feat: complete the native Ontology Studio shell and scoped queries * Recheck review setup under the lock, list reviews by caller, vote per stage Starting a review now rechecks the frozen approval rule and the chosen reviewers against the ontology under the review lock and refuses with 409 when either changed since the request was prepared, so the lock has no window before the release is In Review. The review list starts from the caller's own open approval tasks, which the database narrows to the caller as reviewer, assignee or requester, instead of scanning every release in review and loading its task. A reviewer's vote is recorded once per workflow stage, so a task that moves through several approval stages keeps every stage's vote. Regenerate the API reference so it lists the two review endpoints. * Fixes #36186: Add the Open task label and record the approve-and-publish rule * Cover relationship type reuse after the picker's list went stale The 409 fallback in OntologyGraphView adopted whichever registry type held a planned name. A reused primary lost the inverse it already had, because the inverse was resolved against only the two types in hand, and a reused inverse had its own partner overwritten by the back-link patch. These tests pin the behavior the picker should have and fail on the current code. * Reload the registry before registering a relationship type The picker planned names against a list up to 30 s old and recovered from a 409 by fetching the existing type by name. That treated a name collision as the same type: a reused primary dropped the inverse the registry already held, and a reused inverse was re-pointed at the new primary, orphaning its partner for every glossary and ontology that uses it. The picker now reloads the registry after the dialog. A type registered since the list was cached is reused as it is, with its real inverse. Otherwise names are planned apart from everything the registry holds, and the back link is written only to the inverse this flow just created. A 409 after that is a race and surfaces as an error instead of being papered over. * Clear a lost capture receipt once its revisions have moved, and record the recovery rules * Fixes #36189: Keep clicked tree placement under transitions, name version chip by its text, record template hiding rule * Test ontology review votes through TaskWorkflowHandler.resolveTask A non-final vote leaves the task open and records one vote with its comment. A repeat voter is recorded once, and a voter keeps one vote in each approval stage. * Cover relationship type creation for users who cannot edit registry types A user whose policy grants Create but not EditAll on relationshipType could register a primary type and its inverse and then fail on the back-link patch, which the server authorizes as EditAll. The new tests expect the dialog to disable its inverse field for that user and the flow to write a single type, plain or symmetric, with no patch. The first test fails on the current component. * Offer a registry inverse only to users who can edit relationship types The inverse's back link is a PATCH of /inverse on the inverse type, which the server authorizes as EditAll on relationshipType, and the creator holds no owner right on a type it just made. The create dialog now disables the inverse field for a user without that right and shows the standard permission message under it, so a create-only user registers one type, plain or symmetric, and never leaves a half-linked pair. The decision record gains a v2 amendment with the rule. * Sort imports in the release panel test * Keep release and member ID binds within one parameter budget in definition status queries The status resolver and the unpublished-change counter chunked only the member IDs and bound every release ID into each chunk. A large page could pass the database bind-parameter limit and fail the list request. Both lists now share one budget and every release and member pair is still queried once. * Test that restoring an ontology keeps inverse links between its properties * Keep inverse links between properties that a restore brings back together * Test that relation choices follow the rights their writes need A user who can create axioms but not properties is offered only the relations an axiom completes, and a reused pair is offered from the side whose inverse the user can edit. Submitting a choice the user cannot complete shows the permission message and writes nothing. * Offer only the relations whose writes the user may make The native relation picker offered every registry type to any user who could create axioms. Picking a type can also declare the properties the ontology lacks, which needs create rights on properties, or point a reused property at its partner, which needs edit rights on that property. A user without those rights picked the type, and the server refused the first write. The picker now offers a type only when the user holds every right its planned writes need, and it reads edit rights per property. Submitting a type the user cannot complete shows the permission message before any write. Hierarchy relations and pairs that the ontology already holds linked need only the axiom right, so they stay available. Planning every type on each model refresh made the planner quadratic in the number of properties, because each plan built a set of all property names and scanned the properties by IRI. The planner now indexes the properties by IRI once per model and builds the name set only when it declares a property. With 100 registry types and 10,000 properties, planning the whole catalog takes about 41 ms instead of about 1.9 s, and the plans match the previous planner's. The decision record gains a v3 amendment that states the rule. * Test that restoring an ontology drops a link its partner no longer names back A property deleted on its own keeps its inverse, while the server clears the partner's side. The new case gives the partner another inverse, deletes and restores the ontology, and expects the partner's current pair back with the older claim cleared. It runs every order of the three properties' ids, because a write applies its changes in id order. * Drop a restored inverse that its returning partner no longer names Restoring an ontology also brings back the properties that were deleted on their own. Such a property keeps the inverse it held, but the server cleared its partner's side when it was deleted, and the partner may have taken another inverse since. The restore kept that older claim whenever the partner came back in the same write, so the pair that survived depended on the order of the properties' ids. A link to a partner that the same restore brings back now survives only when the partner's stored inverse names the property. Pairs that were linked on both sides come back as they were, and the older claim is cleared, whatever the id order. A property whose partner was left without an inverse comes back unlinked. A single restore still relinks a live partner and drops a partner that stays deleted. * Test that restoring an ontology drops claims that compete for a live partner A property can be restored on its own inside a deleted ontology. When two properties that were deleted earlier still name it as their inverse, restoring the ontology brings both claims back at once, and both pass the check that the partner is live. The claim that reconciles last wins, so the pair that survives depends on the order of the properties' ids. The new test runs every order of three properties' ids on both engines and expects all three properties to come back unlinked. Two more tests cover the claims an ontology restore keeps. A lone claim on a live partner relinks it. Two claims on a vocabulary property both survive, because the server never writes the side of a vocabulary property. * Drop restored claims that compete for one live partner Restoring an ontology brings back every property deleted in it, including properties that were deleted on their own earlier, and each keeps the inverse it held. A partner they name can already be live, because a property can be restored on its own inside a deleted ontology. When two of the restored properties named the same live partner, both claims passed the check that the partner is live, and the claim that reconciled last won. The pair that survived depended on the order of the properties' ids. A restore states its links all at once, so none of these claims is the latest. When two or more properties that one restore brings back name the same live partner, the restore now clears every one of those claims and leaves the partner as it was. A lone claim on a live partner still relinks it. A claim on a partner that returns in the same restore is still kept only when that partner names the property back. Claims on a vocabulary property that the ontology does not declare never compete, because no write changes that property, so a round trip keeps them. The decision record states the rule beside its restore exception, and its Guard names the test that covers it. * Reword the restore rule in the record and split the restore test setup * Test each ontology review view over stored tasks List the assigned, pending and created views through listReviews on MySQL and PostgreSQL, over approval tasks stored the way TaskRepository stores them: about, the requester and the reviewers live in relationship rows, not in the task JSON. The test database gains user_entity so the task's people resolve. Every view returns no reviews on the current head. * List ontology reviews with the release each task is about TaskRepository keeps a task's about out of the stored JSON and clears it unless the read asks for it. The review list read tasks without about, so it matched no release and returned no rows in any view. * Test that the review list never reads the caller's glossary approvals A steward who reviews glossary approvals and one release review lists pending reviews, and a requester who created them lists created reviews. Each gets the one release, and no statement the list runs names a glossary approval task. * List only the approval tasks that are about an ontology release The review list read every open approval task the caller was on, glossary and data product approvals included, and hydrated each one. The task filter now also requires a task about an ontology release, and the list reads the releases of the listed tasks in one query instead of one query per task. * Name the tasks in the glossary approval list test * Test that a lost capture checks history before it clears Pins two orders the moved-revision clear relies on: history is scanned before a moved revision clears the receipt, and the state is read before the history. Renames the test that guards the unchanged-revision case to say what it covers. * Correct when a stuck capture receipt clears in its decision record * Test that the task button renders the Open task key exactly * Record the native template hiding rule in its own decision record Moves the rule out of the tree-window and count-worker record, which gets back its original title and guard, and cites the new record where the console decides which templates to hide. * Test that the version chip shows the release history tooltip * Keep the review list test from registering a glossary repository Surefire runs every service test class in one JVM, and a repository the test registers stays bound to its database after the class ends. The fixed list never reads a glossary term, so the test now names the term's type on its reference instead of registering the repository. * Test that the review list drops a superseded review task A release can be in review under a new task while the task it was in review under before is still open. The list must show the release once, under the current task. No test covered the check in reviewedRelease that drops the older task, so every test passed without it. * Test that the review list reads its releases in one statement 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. * Order the AI-mode import and space the graph helpers * Show the review state's retry in AI mode AI mode's write actions wait on a readable review state, so its panel now shows the loading line and the retry alert that the classic footer shows, unless its own state-read alert is already showing. New AI cases cover that retry in both views, the single alert after a failed state read, and the storage-unavailable alert. * Test that the version chip is named by its visible version The release-history trigger shows the active version, so a screen reader and a speech user should reach it by that text. * Name the version chip by its visible version The release-history trigger replaced its accessible name with "Release history", so screen readers never read the version it shows and a speech user could not press it by that text. The visible version is now its name, and the purpose moves to the button's tooltip, as the native shell's version chip already does. * Test that Previous returns to page 1 past the cursor history cap The Tasks drawer keeps a bounded history of page cursors for Previous. Paging past the cap should still let Previous reach page 1. * Keep page 1 reachable from a trimmed Tasks drawer history The drawer kept at most 40 Previous cursors by dropping the oldest one, which was page 1. After 40 pages, Previous stopped at the oldest kept page and only First returned to page 1. The trim now keeps page 1 first and the 39 newest cursors after it, so Previous from the oldest kept page goes to page 1. The 25-row page and the 40-cursor cap are named constants, and a new decision record states both values and why: ADR:2026-10-29-studio-tasks-drawer-pages-25-rows-and-keeps-page-one-in-a-40-cursor-history. * Test the Publish count while the ontology list loads With one ontology selected and the ontology list still loading, the Publish count should say it is loading in both the classic and AI headers, not that it is unavailable. * Show loading on the Publish count while the ontology list loads The single-ontology count reads unpublishedChanges from the ontology list. While that list loaded, the lookup found nothing, so the Publish button and the AI badge said Unavailable even when the selected ontology had already resolved. The count now says Loading while the list loads, and Unavailable only when the list failed or the loaded entry has no count, as the All-ontologies count already does. * Test that a retried relation type finishes the pair a failed write left 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. * Test the version chip's tooltip The chip's purpose moved from its accessible name into its tooltip, and no case read the tooltip, so dropping it failed nothing. The case now focuses the chip by keyboard and reads the tooltip's text. renderNativeStudio now gives its ontology argument to getOntology and listOntologies as well as the scopes mock, so a case that passes an ontology sees the same one everywhere. * Correct the Tasks drawer paging record and bound the long paging case The record said neither list returns a before cursor. The review queue returns an offset before cursor; only the release events list lacks one. The Context and Consequences now say what each list returns and why the drawer keeps one client history for both. The case that pages past the kept history presses Next and Previous more than 80 times and runs close to Jest's 5 s default. It now has an explicit 20 s timeout, so a slow worker does not fail it and the cases after it. * Finish a relation type pair on retry after a registry write fails 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. * Test that a retry reuses no other inverse and draws the relation once 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. * Test that a relabelled retry finishes the earlier pair first 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. * Finish the earlier pair before planning a relabelled 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. * Test that a failed repair keeps the inverse and a reverse retry links it 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. * Check the property writes when a retry is drawn as the inverse 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. * Look up the retry label in the repaired registry 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. * Bring the merged Studio tests in line with the native shell OntologyGraphView now requires onRequestGraphSurface, so the seven inverse-pair cases that PR4's review round added render with onRequestGraphSurface={jest.fn()} and change nothing else. The native shell's OntologyVersionChip shows an active release as v<semanticVersion>, so the version chip case that review publish added finds the chip by the name v1.2.0. The merge already gave this branch's renderNativeStudio review publish's ontology argument, fed to the scopes mock, getOntology and listOntologies, with nativeOntology as the default for every other caller. * Test that the version chip is disabled while the ontology list loads The page disables the version chip while the scopes hook reports the ontology list as loading or failed, but no case checked it. Add one row for each state that asserts the chip is disabled. * Fit the concept asset tests to PR12's binding contract and the presentation prop PR12's binding cases in OntologyConceptAssets.test.tsx render OntologyConceptAssets without presentation, which this branch's e8b13be made required. tsc rejected both renders, and all six cases threw on presentation.kind. Both renders now pass presentation={{ kind: 'edit' }}, the mode they test. This branch's four cases in that file tested the binding contract PR12 replaced, so the merge kept PR12's file. They are ported onto PR12's axiosClient harness with the same names and intent, except where PR12 changed the outcome. Revisions now come from the re-read after each write, and the remove call takes the request object. A refused write now stops the batch with the unconfirmed status and keeps the unsent selection checked, in place of a per-asset error. The add button now shows while bindings load and stays disabled until a read succeeds, where before it was hidden. PR12 changed getOntologyConceptBindings to return a snapshot tagged kind: 'complete'. Four of this branch's mocks in OntologyConceptInspector.test.tsx lacked the tag, and tsc rejected them. They now carry it. * Read every page of bindings before counting a deprecated concept's assets The binding read on main now returns one page of at most 100 visible assets, and a page can end short with a cursor when hidden rows use up its scan budget. The deprecation impact now follows each after cursor until it is null, then sorts and counts every visible asset. The release resource builds its binding store with the registered asset tables, matching the store's new constructor. Both call sites failed to compile against main. The decision record now describes the paged read. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
… Studio Playwright failures (#36645) * fix(ui): record Studio Tasks drawer counts in the Classic tour fixtures #36188 mounted OntologyTasksDrawer on the Ontology Explorer page; its pending/created count queries run on mount, so the Classic product tour sent them to the server and ProductTour.spec.ts failed on every merge-queue run. * fix(ui): repair the ontology Studio Playwright failures from #36187-#36190 - OntologyReleaseReviewers: allow an empty collection once text is typed. The search starts on focus, so its results land after a fast fill; React Aria only opens the list on a text change with options present, so the reviewer options never appeared (OntologyReview.spec.ts:348). - OntologyStudioHeader (AI mode): move the scope menu and actions onto the wrapping tab row. PageHeader's actions slot never shrinks, so at 924px beside the Governance panel the Tasks button ran ~95px past the viewport and the subtitle collapsed to one word per line. - OntologyNativeTreeShell.spec.ts: drop the one-row assertion; the AI header puts the mode tabs on their own row by design. - OntologyNativeOperations.spec.ts: since #36187 only a definitive rejection offers Retry review, on ontology-recovery-action; a 5xx only offers Check status. Mock a 422 and use that button. * test(playwright): expect the sentence-case Ontology library title #35989 renders the library drawer title sentence-case; #36190's spec still expected title case. It never ran that far before, because it failed earlier at the 924px header check. * test(playwright): load reviewer options before typing in OntologyReview The reviewer picker searches only once focused, and React Aria opens its list only on a text change while options are already loaded, so filling the whole name at once left the list closed. Click the picker and wait for its first user search before typing. Reverts the allowsEmptyCollection change: it read the previous render's search text, so it never opened the list for a single fill. * test(playwright): note why OntologyReview loads reviewer options first * test(playwright): match the disabled approval-rule select in OntologyReview In a locked review the rule control also renders a focusable tooltip trigger with the same name, so getByRole('button', { name: 'All' }) hit a strict-mode violation. The step never ran before the reviewer picker step was fixed.



The stack targets release 2.1.0.
PR1 #35990, PR2 #35994, PR3 #35997, PR4 #35995, PR5 #36183, PR6 #36187, PR7 #36188, PR10 #35991, PR12 #35996 and PR15 #35989 are merged.
This PR targets
main. It has no published child. PR9, PR11, PR13, PR14 and PR16 are not published yet.Current assessment for
b873e9a2ae8, recorded 2026-10-30.Verdict. PASS. The review-bot round passed at
5dba793ba74, and the merge withmain's AI-mode restyle passed at96bb61c28d0. After PR4 landed, a scoped independent review found that no test checked the version chip's disabled rule, and the test-only commitf08687034d3that adds a case for it passed a scoped review. A scoped independent review of the cascade that brought PR10's and PR12's landings passed this PR at051024660b3.What PR12 changed here. PR12 rewrote concept bindings as rows with a paged read, and replaced the binding contract that PR8's asset tests used. In
OntologyConceptAssets.tsx, the merge keeps PR12's Card rows and their remove button and maps them over PR8'svisibleAssets. View shows three assets and a link that opens Data, and Edit keeps the ten-row pager, in both modes. Both sides addedOntologyConceptAssets.test.tsx, and the merge takes PR12's file. The test commitcf81d6493b7passespresentation={{ kind: 'edit' }}to PR12's two renders, tags four PR8 mocks withkind: 'complete', and ports PR8's four asset cases onto PR12's harness. The review found that each ported case still pins a PR8 behavior, and that each outcome change comes from PR12's code. Three mutants of the merged code each failed a case.What moved since the review. Later merges brought PR5's, PR6's and PR7's landings and
mainafter PR15 (#35989) and the per-server credential cache (#36217). Twenty-two files merged without a conflict, the 21 locale files and the Playwright impact map, each with separate entries from both sides. This head records PR7's squashe33bd3dab80with the ours strategy, which leaves the tree unchanged. Against the squash, PR8's patch-id is0215f6f9583, moved from the reviewedc03841e1f3fby context lines only, and every file's changed lines equal the reviewed diff in all 64 files, so the verdict carries.The reviews declared four minor issues that do not block.
Test evidence. The cascade review's gates ran at
051024660b3, which has this PR's changed lines. Jest passes 131 of 131 in the concept assets, concept inspector, graph view and explorer page suites, and ESLint and Prettier are clean on the filescf81d6493b7changes. The cascade lane's Jest run passed 296 of 296 in 18 suites, with the i18n check, the Playwright impact map and CI's lint sequence clean. On this head's tree, the i18n check exits 0 and TypeScript reports onlymain's error atIngestionWorkflowForm.tsx(413,9). The Java impact map check passed on this push.CI. Checks at
b873e9a2ae8were still running when this block was written. Atc74ed0d02b9, the only failing checks wereValidate PR Metadataandmaven-collate-ci, because its base was PR7's branch.Still owed. None of these has run yet. Run each IT on
-Pmysql-elasticsearchand then-Ppostgres-opensearch, and confirm aTests run:line.OntologyNativeTreeShell.spec.ts,OntologyStudioIsolatedToggle.spec.tsandOntologyNativeOperations.spec.tsagainst a live stack built from this head. They drive classic mode.data-tour-step="ontology-studio"frommainand PR8's layout classes.IndependentOntologyStatusIT,IndependentOntologyReleaseValidationITand the review-list ITs listed on PR6, then every merge-queue IT, run from this head because it holds every merge below it.AI mode.
main's Ontology Studio AI-mode restyle #36433 now gates new screens on AI mode. Paths are underopenmetadata-ui/src/main/resources/ui/src/components/. AI mode lacks four PR8 controls, which render only in classic branches. The scope menu's Edit ontology item is classic-only ingovernance/ontology/OntologyStudioScopeMenu/OntologyStudioScopeMenu.tsx, so AI mode reaches Edit only from the Edit mode tab. The concept rail's lifecycle badge and owning-ontology line are classic-only ingovernance/ontology/OntologyConceptInspector/OntologyConceptInspector.tsx. The native concept tree inOntologyExplorer/NativeOntologyTreeView.tsxhas none ofmain's AI restyle. The 924 px top-bar fit is classic-only ingovernance/ontology/OntologyStudioHeader/OntologyStudioHeader.tsx.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.
Describe your changes
Fixes #36189
Studio now uses the native ontology shell, scoped tree and lifecycle inspector. Scope controls, version state, aggregate counts, import/export and graph selection follow the active ontology. Polyhierarchies retain every parent through bounded tree windows.
The Query console waits for its persisted library before creating a draft. Native shared-query display filters only exact unchanged bundled glossary templates, preserves authored and edited records, and keeps complete arrays for replacement writes. Generated query text is consumed only in its owning scope. Aggregate Query retains its owner selector.
This is PR8 of the sixteen-part Ontology Studio program for release 2.1.0. It targets PR7, #36188. PR9 adds class authoring on this branch. This remains a draft while production browser and performance gates are open.
Type of change
High-level design
The page retains scope and mode ownership, and forwards scoped selection and authoring requests to the native workspace. The inspector exposes definition status and authorized asset actions. The version chip and unpublished count use current native state with bounded permission work.
The tree and count-worker decision records the 2,048-placement tree window and four concurrent count workers. Existing PR6 publication recovery and PR7 task identity, fixture and badge-test repairs are preserved.
Tests
Use cases covered
Native scope and shell controls, aggregate behavior, polyhierarchy windows, inspector status and assets, graph selection, permissions and counts, Query readiness, exact canonical seed matching, full replacement writes, and scoped generated-text transfer.
Unit tests
At
2d614805dc5, the merge gate ran 267 Jest tests in 17 suites and 450 Java unit tests, with no failures. An earlier focused run passed 344 assertions across 24 suites. Seven standalone controls exercise the actual visible-text scanner with an explicit jsdom visibility shim. Those controls establish selector and text behavior, not browser layout or visibility acceptance.Organize-imports, ESLint, Prettier, license checks, locale synchronization and app-doc generation pass. All 60 source hashes and the excluded parent paths match the reviewed integration. TypeScript 7.0.2 reports 0 app errors at
2d614805dc5.Backend and ingestion integration tests
This PR changes frontend source and its design record. Backend contracts come from the preceding stack entries.
Playwright and UI recording
OntologyNativeTreeShell.spec.tsand the isolated-toggle regression are included. Current production build, the repaired Query browser flow, ten live lanes, the 924px layout, screenshots and video remain open. Historical checks do not establish the behavior of this newly assembled head.Performance
The original 500-concept load comparison and 50-multiparent tree-window measurements remain open. No performance threshold was changed.
Checklist