Skip to content

fix(teams): prevent circular team hierarchy on create/update paths#30063

Merged
mohityadav766 merged 4 commits into
mainfrom
team-hierarchy-cycle-check
Jul 20, 2026
Merged

fix(teams): prevent circular team hierarchy on create/update paths#30063
mohityadav766 merged 4 commits into
mainfrom
team-hierarchy-cycle-check

Conversation

@mohityadav766

@mohityadav766 mohityadav766 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Describe your changes:

Fixes #

TeamRepository.validateHierarchy() only walked team.getParents(), so a cycle introduced through team.getChildren() (the other direction of the same PARENT_OF edge) or by naming the same team as both a parent and a child in one request was persisted and later tripped the runtime "Circular dependency detected" guard in SubjectContext. This validates both edge directions and rejects parent/child overlap in prepare(), closing the gap for POST, PUT, PATCH, bulk create and CSV import (all paths funnel through prepare()).

Type of change:

  • Bug fix

High-level design:

N/A — small, self-contained change to one validation method.

Tests:

Use cases covered

  • Updating a team to add one of its ancestors as a child is rejected as a circular reference
  • Creating a team that names the same team as both a parent and a child is rejected

Backend integration tests

  • Added in openmetadata-integration-tests/.../TeamResourceIT.java: test_teamHierarchy_childCannotBeAncestor and test_teamHierarchy_parentAndChildOverlapRejected.
  • Existing test_importCsv_circularDependency_* continue to pass (message contract preserved).

Playwright (UI) tests

  • Not applicable (no UI changes).

UI screen recording / screenshots:

Not applicable.

Checklist:

  • I have read the CONTRIBUTING document.
  • I have added a test that covers the exact scenario we are fixing.

Greptile Summary

This PR prevents circular team hierarchies during team create and update flows. The main changes are:

  • Validates proposed parent edges and proposed child edges.
  • Walks proposed ancestors before the new team has been persisted.
  • Rejects requests that name the same team as both parent and child.
  • Adds integration tests for child-cycle and create-time cross-edge cases.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java Adds bidirectional team hierarchy validation and create-time ancestor traversal.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TeamResourceIT.java Adds integration tests for rejected circular team hierarchy requests.

Reviews (4): Last reviewed commit: "Merge branch 'main' into team-hierarchy-..." | Re-trigger Greptile

Context used (3)

  • Context used - CLAUDE.md (source)
  • Context used - openmetadata-ui-core-components/CLAUDE.md (source)
  • Context used - AGENTS.md (source)

validateHierarchy only walked team.getParents(), so a cycle introduced via
team.getChildren() (the other direction of the PARENT_OF edge), or by naming the
same team as both parent and child in one request, slipped through and later
tripped the runtime guard in SubjectContext. Validate both edge directions and
reject parent/child overlap in prepare(), which covers POST, PUT, PATCH, bulk
create and CSV import. Adds integration tests for the child-ancestor and
overlap cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 14, 2026 21:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (29 flaky)

✅ 4540 passed · ❌ 0 failed · 🟡 29 flaky · ⏭️ 95 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 438 0 2 16
✅ Shard 2 11 0 0 0
🟡 Shard 3 819 0 13 8
🟡 Shard 4 821 0 1 18
🟡 Shard 5 838 0 2 5
🟡 Shard 6 787 0 1 46
🟡 Shard 7 826 0 10 2
🟡 29 flaky test(s) (passed on retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: metric (shard 1, 1 retry)
  • Flow/SearchRBAC.spec.ts › the browse tree only shows the asset-type categories a user can access (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 3, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary Term (Nested) (shard 3, 1 retry)
  • Features/BulkImportWithDotInName.spec.ts › Import at database level with dot in service name (shard 3, 1 retry)
  • Features/ContextCenterArchive.spec.ts › archive page lazy-loads more rows on scroll within its own scroll container (shard 3, 2 retries)
  • Features/ContextCenterArticles.spec.ts › Article listing search filters, clears, and shows empty state (shard 3, 1 retry)
  • Features/ContextCenterArticles.spec.ts › Article list cards, recently viewed widget, and pagination work (shard 3, 1 retry)
  • Features/ContextCenterArticles.spec.ts › description: switching articles does not bleed unsaved content into next article (shard 3, 1 retry)
  • Features/ContextCenterDocument.spec.ts › shows header with Upload File button (shard 3, 1 retry)
  • Features/ContextCenterDocument.spec.ts › bulk move moves selected documents to a folder with a single API call and folder name appears on both cards (shard 3, 1 retry)
  • Features/ContextCenterMemories.spec.ts › adding a linked asset in edit mode shows entity badge on the row (shard 3, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with view-only permission cannot see restore or delete actions on an archived document (shard 3, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with editAll permission can see restore action but not delete action on an archived document, and can restore it (shard 3, 1 retry)
  • Features/ContextCenterPermission.spec.ts › user with deleteAll permission can see delete action but not restore action on an archived document, and can delete it (shard 3, 1 retry)
  • Features/IncidentManager.spec.ts › Complete Incident lifecycle with table owner (shard 4, 1 retry)
  • Flow/PersonaDeletionUserProfile.spec.ts › User profile loads correctly before and after persona deletion (shard 5, 1 retry)
  • Pages/CustomProperties.spec.ts › Set & Update all CP types on pipeline (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should perform CRUD and Removal operations for dashboardDataModel (shard 6, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should NOT show restricted edit buttons for Data Steward for dashboardDataModel (shard 7, 1 retry)
  • Pages/ExplorePageRightPanel.spec.ts › Should allow Data Steward to edit glossary terms for searchIndex (shard 7, 1 retry)
  • Pages/Glossary.spec.ts › Approve and reject glossary term from Glossary Listing (shard 7, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Import partial success - some terms pass, some fail (shard 7, 1 retry)
  • Pages/GlossaryImportExport.spec.ts › Glossary CSV import preserves typed relations (shard 7, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify Impact Analysis service filter selection (shard 7, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for pipelineService in platform lineage (shard 7, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for apiService in platform lineage (shard 7, 1 retry)
  • Pages/TestSuite.spec.ts › Logical TestSuite (shard 7, 1 retry)
  • Pages/UserDetails.spec.ts › Admin user can get all the roles hierarchy and edit roles (shard 7, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Address PR review findings. A single create request with parents=[P] and
children=[C] where C is an ancestor of P slipped through, because the per-edge
walks read only persisted state and the new team has no id yet. Collect the
team's ancestor names once by walking up from both its proposed (in-request) and
persisted parents, then check each child against that set. This catches the
create-time cross-edge cycle and replaces the per-child re-walk, cutting the
children check from O(children*depth) to O(depth) DB queries. Removes the
now-subsumed parent/child overlap helper. Adds an integration test for the
create-time cross-edge cycle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 15, 2026 06:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gitar-bot

gitar-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 resolved / 2 findings

Tightens team hierarchy validation by implementing bidirectional checks in TeamRepository to prevent circular dependencies. Addresses issues with cross-edge cycles and redundant ancestor walking while successfully adding covering integration tests.

✅ 2 resolved
Edge Case: Cross-edge cycle from new parent+new child not caught

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1483-1497
Both per-edge walks read the pre-update DB state, and the overlap check only catches identical parent/child names. A single request that adds parent A and child C where C is already an ancestor of A (edges C->...->A exist in DB) forms the cycle C->...->A->T->C, but neither walk detects it: walking up from A never reaches the not-yet-persisted T, and walking up from T never sees the not-yet-persisted A edge. To close this, run checkCircularReference over the union of the request's proposed parent and child edges (treating in-request parents as edges into T when validating children), not just against persisted state.

Performance: Ancestor chain re-walked once per child

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1491-1493 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1521-1531 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1538-1552
In the children loop, validateNoCircularReference->checkCircularReference walks up team's full ancestor chain (a findFrom DB query per level) for every child, even though that upward walk is identical regardless of which child is being checked (only originalTeamName differs). For a team with many children and a deep hierarchy this is O(children * depth) redundant queries per create/update. Compute the set of ancestor names of the team once, then check each child name against that set.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copilot AI review requested due to automatic review settings July 15, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 16, 2026 12:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sonarqubecloud

Copy link
Copy Markdown

@mohityadav766
mohityadav766 merged commit 659f288 into main Jul 20, 2026
65 of 68 checks passed
@mohityadav766
mohityadav766 deleted the team-hierarchy-cycle-check branch July 20, 2026 07:00
@gitar-bot

gitar-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 resolved / 2 findings

Tightens team hierarchy validation by implementing bidirectional checks in TeamRepository to prevent circular dependencies. Addresses issues with cross-edge cycles and redundant ancestor walking while successfully adding covering integration tests.

✅ 2 resolved
Edge Case: Cross-edge cycle from new parent+new child not caught

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1483-1497
Both per-edge walks read the pre-update DB state, and the overlap check only catches identical parent/child names. A single request that adds parent A and child C where C is already an ancestor of A (edges C->...->A exist in DB) forms the cycle C->...->A->T->C, but neither walk detects it: walking up from A never reaches the not-yet-persisted T, and walking up from T never sees the not-yet-persisted A edge. To close this, run checkCircularReference over the union of the request's proposed parent and child edges (treating in-request parents as edges into T when validating children), not just against persisted state.

Performance: Ancestor chain re-walked once per child

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1491-1493 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1521-1531 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java:1538-1552
In the children loop, validateNoCircularReference->checkCircularReference walks up team's full ancestor chain (a findFrom DB query per level) for every child, even though that upward walk is identical regardless of which child is being checked (only originalTeamName differs). For a team with many children and a deep hierarchy this is O(children * depth) redundant queries per create/update. Compute the set of ancestor names of the team once, then check each child name against that set.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

mohityadav766 added a commit that referenced this pull request Jul 20, 2026
…30063)

* fix(teams): reject circular hierarchy from child edge and overlap

validateHierarchy only walked team.getParents(), so a cycle introduced via
team.getChildren() (the other direction of the PARENT_OF edge), or by naming the
same team as both parent and child in one request, slipped through and later
tripped the runtime guard in SubjectContext. Validate both edge directions and
reject parent/child overlap in prepare(), which covers POST, PUT, PATCH, bulk
create and CSV import. Adds integration tests for the child-ancestor and
overlap cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(teams): detect cross-edge cycles on create, walk ancestors once

Address PR review findings. A single create request with parents=[P] and
children=[C] where C is an ancestor of P slipped through, because the per-edge
walks read only persisted state and the new team has no id yet. Collect the
team's ancestor names once by walking up from both its proposed (in-request) and
persisted parents, then check each child against that set. This catches the
create-time cross-edge cycle and replaces the per-child re-walk, cutting the
children check from O(children*depth) to O(depth) DB queries. Removes the
now-subsumed parent/child overlap helper. Adds an integration test for the
create-time cross-edge cycle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 659f288)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants