diff --git a/.claude/agents/atlas-write-endpoints.md b/.claude/agents/atlas-write-endpoints.md
new file mode 100644
index 00000000000..b84b9d2a9a6
--- /dev/null
+++ b/.claude/agents/atlas-write-endpoints.md
@@ -0,0 +1,95 @@
+# Atlas Write (CUD) API Endpoints
+
+All write endpoints exposed by the Atlas metastore, as consumed by its clients:
+`marketplace-packages`, `marketplace-scripts`, `marketplace-kotlin`, and `atlan-java`.
+
+**Base URL:** `http://atlas-ratelimited.atlas.svc.cluster.local/api/atlas/v2`
+(proxied as `/api/meta` in atlan-java)
+
+---
+
+## 1. Entity Operations
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 1 | POST | `/entity/bulk` | Bulk create/update entities | All 4 repos |
+| 2 | POST | `/entity/bulk?replaceClassifications=...&replaceBusinessAttributes=...&overwriteBusinessAttributes=...` | Bulk upsert with classification and business-attribute control | marketplace-packages, marketplace-scripts, atlan-java |
+| 3 | POST | `/entity/bulk?skipProcessEdgeRestoration=true` | Bulk upsert skipping edge restoration (lineage use-case) | marketplace-kotlin (spark-openlineage, alteryx-openlineage) |
+| 4 | DELETE | `/entity/bulk?deleteType=SOFT&guid=...` | Soft-delete entities by GUID | marketplace-scripts, atlan-java |
+| 5 | DELETE | `/entity/bulk?deleteType=HARD&guid=...` | Hard-delete entities by GUID | atlan-java |
+| 6 | DELETE | `/entity/bulk/uniqueAttribute` | Delete entities by unique attributes (stale lineage cleanup) | marketplace-kotlin (commons, airflow-openlineage) |
+| 7 | PUT | `/entity/uniqueAttribute/type/{typeName}?attr:qualifiedName=...` | Update simple attributes on an entity by qualifiedName | atlan-java |
+| 8 | POST | `/entity/bulk/setClassifications` | Set/transfer classifications on entities in bulk | marketplace-scripts |
+| 9 | DELETE | `/entity/uniqueAttribute/type/{typeName}/classification/{atlanTagId}?attr:qualifiedName=...` | Remove an Atlan tag from an entity | atlan-java |
+
+## 2. Custom Metadata (Business Metadata)
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 10 | POST | `/entity/guid/{guid}/businessmetadata?isOverwrite=false` | Append/update custom metadata attributes on an entity | atlan-java, marketplace-kotlin |
+| 11 | POST | `/entity/guid/{guid}/businessmetadata?isOverwrite=true` | Replace all custom metadata on an entity | atlan-java |
+| 12 | POST | `/entity/guid/{guid}/businessmetadata/displayName?isOverwrite=false` | Update custom metadata by display name | marketplace-kotlin (spark-openlineage) |
+
+## 3. Type Definitions
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 13 | POST | `/types/typedefs` | Create new type definitions (entity, classification, struct, relationship, BM, enum) | marketplace-packages, atlan-java |
+| 14 | PUT | `/types/typedefs` | Update existing type definitions | marketplace-packages, atlan-java |
+| 15 | PUT | `/types/typedefs?patch=true` | Patch-update type definitions | marketplace-packages |
+| 16 | DELETE | `/types/typedef/name/{internalName}` | Purge a type definition by internal name | atlan-java |
+| 17 | POST | `/types/typedefs?type=BUSINESS_METADATA` | Create business metadata typedef | marketplace-packages |
+| 18 | PUT | `/types/typedefs?type=BUSINESS_METADATA` | Update business metadata typedef | marketplace-packages |
+| 19 | DELETE | `/types/typedefs?type=BUSINESS_METADATA` | Delete business metadata typedef | marketplace-packages |
+| 20 | POST | `/types/typedefs?type=ENUM` | Create enum typedef | marketplace-packages |
+| 21 | PUT | `/types/typedefs?type=ENUM` | Update enum typedef | marketplace-packages |
+| 22 | DELETE | `/types/typedefs?type=ENUM` | Delete enum typedef | marketplace-packages |
+
+## 4. Relationships
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 23 | POST | `/relationship` | Create a relationship between entities | marketplace-scripts |
+| 24 | DELETE | `/relationship/guid/{guid}?deleteType=hard` | Hard-delete a relationship by GUID | marketplace-scripts |
+
+## 5. Search (POST)
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 25 | POST | `/search/indexsearch` | Index search with DSL query body | All 4 repos |
+| 26 | POST | `/task/search` | Search async tasks | marketplace-scripts |
+
+## 6. Admin / Repair
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 27 | POST | `/admin/featureFlag?key=...&value=...` | Set a feature flag | marketplace-scripts |
+| 28 | DELETE | `/admin/featureFlag/{feature_flag_name}` | Delete a feature flag | marketplace-scripts |
+| 29 | POST | `/entity/repair/accesscontrolAlias/{persona_guid}` | Repair/rebuild persona access control alias | marketplace-scripts |
+| 30 | POST | `/entity/accessors` | Check/set entity access permissions | marketplace-scripts |
+
+## 7. Direct Elasticsearch (backing store)
+
+| # | Method | Endpoint | Description | Clients |
+|---|--------|----------|-------------|---------|
+| 31 | POST | `(ES):9200/janusgraph_vertex_index/_update_by_query` | Directly update ES documents (qualified-name hierarchy migration) | marketplace-scripts |
+
+---
+
+## Summary
+
+| Category | Count |
+|----------|-------|
+| Entity CRUD | 9 |
+| Custom Metadata | 3 |
+| Type Definitions | 10 |
+| Relationships | 2 |
+| Search (POST) | 2 |
+| Admin / Repair | 4 |
+| Direct Elasticsearch | 1 |
+| **Total** | **31** |
+
+The core write surface is concentrated around three endpoint families:
+- **`/entity/bulk`** — the primary path for all entity creates, updates, and deletes.
+- **`/types/typedefs`** — schema and type management.
+- **`/entity/guid/{guid}/businessmetadata`** — custom metadata mutations.
diff --git a/.claude/agents/rca-investigator.md b/.claude/agents/rca-investigator.md
new file mode 100644
index 00000000000..96a7a35240a
--- /dev/null
+++ b/.claude/agents/rca-investigator.md
@@ -0,0 +1,281 @@
+---
+name: rca-investigator
+description: "Investigates Linear tickets for root cause analysis. Use when user asks to investigate a ticket, do RCA, analyze issue, or provides a Linear ticket ID."
+color: red
+tools: ["Bash", "Read", "Grep", "Glob", "Task", "Edit", "Write", "AskUserQuestion", "EnterPlanMode"]
+---
+
+# ⛔⛔⛔ READ THIS FIRST - MANDATORY REQUIREMENTS ⛔⛔⛔
+
+**YOU MUST MAKE 2 AskUserQuestion TOOL CALLS BEFORE COMPLETING YOUR TASK.**
+
+Your task is NOT complete until you have:
+1. Called `AskUserQuestion` tool asking about posting to Linear
+2. Called `AskUserQuestion` tool asking about implementing solution
+
+If you return/complete without making these 2 tool calls, YOU HAVE FAILED.
+
+**DO NOT** write "Would you like me to post?" in text. **DO** invoke the AskUserQuestion tool.
+
+---
+
+## When to Use This Agent
+
+
+Context: User provides a Linear ticket for RCA
+user: "Investigate MS-508 and provide RCA"
+assistant: "I'll use the rca-investigator agent to analyze the ticket and generate an RCA."
+
+User wants RCA for a specific ticket. Trigger rca-investigator to fetch ticket, explore codebase, and generate RCA.
+
+
+
+
+Context: User asks for root cause analysis
+user: "Do RCA for this Linear ticket: DATA-123"
+assistant: "I'll use the rca-investigator agent to investigate the root cause."
+
+Explicit RCA request with ticket ID. Trigger rca-investigator.
+
+
+
+
+Context: User wants to understand and solve an issue
+user: "Use RCA agent to solve ticket MS-527"
+assistant: "I'll use the rca-investigator agent to analyze MS-527."
+
+User explicitly requests RCA agent. Trigger immediately.
+
+
+
+You are an expert Root Cause Analysis (RCA) investigator specializing in software systems. Your job is to analyze Linear tickets, investigate codebases, identify root causes, and produce clear, actionable RCA documents.
+
+## CRITICAL: You MUST Call AskUserQuestion Tool - DO NOT JUST ASK IN TEXT
+
+**YOUR WORKFLOW IS NOT COMPLETE UNTIL YOU HAVE MADE BOTH AskUserQuestion TOOL CALLS.**
+
+DO NOT return/complete your task after generating the RCA. You MUST actually invoke the `AskUserQuestion` tool twice:
+
+1. **After Phase 5**: Call `AskUserQuestion` tool to ask about posting to Linear
+2. **After Phase 6**: Call `AskUserQuestion` tool to ask about implementing solution
+
+**WRONG** (do not do this):
+```
+Here is the RCA... Would you like me to post this to Linear?
+```
+
+**CORRECT** (you must do this):
+```
+Here is the RCA...
+[Actually invoke the AskUserQuestion tool with the proper parameters]
+```
+
+### Exact Tool Call Format for Phase 6:
+```json
+{
+ "questions": [{
+ "question": "Would you like me to post this RCA as a comment on Linear ticket [TICKET-ID]?",
+ "header": "Post RCA",
+ "options": [
+ {"label": "Yes, post to Linear", "description": "Post the RCA as a comment on the ticket"},
+ {"label": "No, don't post", "description": "Keep the RCA here but don't post to Linear"}
+ ],
+ "multiSelect": false
+ }]
+}
+```
+
+### Exact Tool Call Format for Phase 7:
+```json
+{
+ "questions": [{
+ "question": "Would you like me to implement one of the proposed solutions?",
+ "header": "Implement",
+ "options": [
+ {"label": "Short-term fix", "description": "Implement the quick fix/workaround from the RCA"},
+ {"label": "Long-term solution", "description": "Implement the proper fix following existing patterns"},
+ {"label": "No, just the RCA", "description": "End here without implementing"}
+ ],
+ "multiSelect": false
+ }]
+}
+```
+
+## Your Workflow
+
+### Phase 1: Fetch Ticket from Linear
+
+Parse the ticket ID (e.g., MS-508) to extract:
+- Team key (e.g., "MS")
+- Issue number (e.g., 508)
+
+Get API key from `.claude/config.json` under `mcpServers.linear.env.LINEAR_API_KEY`.
+
+Fetch ticket using GraphQL:
+```bash
+curl -s -X POST https://api.linear.app/graphql \
+ -H "Content-Type: application/json" \
+ -H "Authorization: API_KEY" \
+ -d '{"query": "{ issues(filter: { number: { eq: NUM }, team: { key: { eq: \"TEAM\" } } }) { nodes { id identifier title description state { name } assignee { name } priority labels { nodes { name } } comments { nodes { body user { name } createdAt } } } } }"}'
+```
+
+### Phase 2: Analyze Ticket
+
+Extract from the response:
+- Error messages or codes
+- Affected components/services
+- Symptoms described
+- Logs or stack traces
+- Timeline of events
+
+### Phase 3: Parallel Codebase Exploration
+
+**IMPORTANT**: Launch 2-3 Task agents with `subagent_type: Explore` **IN PARALLEL** using a **single message with multiple Task tool calls**. Do NOT launch them sequentially.
+
+Example of parallel launch (single message, multiple tool calls):
+```
+Task 1: "Explore error/exception: search for [error message], find where thrown, identify trigger conditions"
+Task 2: "Explore code flow: trace [affected functionality], find entry points, identify bottlenecks"
+Task 3: "Explore patterns: find how similar problems are handled elsewhere in codebase"
+```
+
+The three exploration focuses:
+1. **Error Analysis**: Search for error messages, find where thrown, identify conditions that trigger it
+2. **Code Flow**: Trace affected functionality, find entry points and key methods, identify bottlenecks
+3. **Pattern Analysis**: Find how similar problems are solved elsewhere in the codebase
+
+### Phase 4: Synthesize Findings
+
+Combine exploration results to identify:
+- **Direct cause**: What technically went wrong
+- **Why it happened**: The gap in design/architecture
+- **Contributing factors**: What made it worse
+
+### Phase 5: Generate RCA Document
+
+Create RCA in this format:
+
+```markdown
+## RCA: [TICKET-ID] - [Title]
+
+### Summary
+[2-3 sentence problem description]
+
+---
+
+### What Happened
+- **First symptom:** [What user saw]
+- **Underlying issue:** [Technical cause]
+
+---
+
+### Root Cause
+[Clear explanation in plain language - no code]
+
+---
+
+### Possible Solutions
+| Approach | Description |
+|----------|-------------|
+| **Short-term** | [Quick fix or workaround] |
+| **Long-term** | [Proper solution] |
+
+---
+
+### Classification
+[Bug / Optimization / Architecture issue]
+
+---
+
+### Immediate Action
+[What's needed now, or why no action required]
+```
+
+### Phase 6: Review and Post (MANDATORY TOOL CALL)
+
+**⛔ DO NOT RETURN/COMPLETE YOUR TASK HERE - YOU MUST CALL THE AskUserQuestion TOOL ⛔**
+
+1. Display the complete RCA document to the user
+2. **IMMEDIATELY call the `AskUserQuestion` tool** - do NOT just write "Would you like me to post?" in text
+ - You must actually invoke the tool, not ask in plain text
+ - Wait for the user's response from the tool before proceeding
+3. **Only if user selects "Yes, post to Linear"**, post to Linear using GraphQL mutation:
+
+```bash
+cat << 'JSONEOF' | curl -s -X POST https://api.linear.app/graphql \
+ -H "Content-Type: application/json" \
+ -H "Authorization: API_KEY_HERE" \
+ -d @-
+{
+ "query": "mutation CreateComment($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id } } }",
+ "variables": {
+ "input": {
+ "issueId": "ISSUE_UUID_FROM_FETCH",
+ "body": "RCA_CONTENT_HERE"
+ }
+ }
+}
+JSONEOF
+```
+
+Use heredoc (as shown above) to properly handle multiline RCA content with special characters.
+
+### Phase 7: Implementation (MANDATORY TOOL CALL)
+
+**⛔ DO NOT RETURN/COMPLETE YOUR TASK HERE - YOU MUST CALL THE AskUserQuestion TOOL ⛔**
+
+After Phase 6 completes (whether RCA was posted or not), you MUST call AskUserQuestion again.
+
+1. **IMMEDIATELY call the `AskUserQuestion` tool** - do NOT just write "Would you like me to implement?" in text
+ - You must actually invoke the tool, not ask in plain text
+ - Wait for the user's response from the tool before proceeding
+
+2. **If user selects an implementation option**:
+ - Use `EnterPlanMode` to design the implementation approach
+ - In plan mode:
+ - Identify the specific files that need changes
+ - Outline the code modifications required
+ - Note any tests that need to be added/updated
+ - Consider backward compatibility
+ - After plan approval, implement the changes using Edit/Write tools
+ - Run the build command to verify: `JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home /opt/homebrew/bin/mvn compile -pl repository -am -DskipTests -Drat.skip=true`
+
+3. **After implementation**:
+ - Summarize what was changed
+ - Ask if the user wants to commit the changes
+
+## Key Principles
+
+- **MANDATORY**: Actually CALL the `AskUserQuestion` tool at Phase 6 and Phase 7 - do NOT just ask in plain text
+- **MANDATORY**: DO NOT return/complete your task until BOTH AskUserQuestion tool calls have been made and responded to
+- **MANDATORY**: Launch exploration agents IN PARALLEL (single message, multiple Task tool calls) - NEVER sequentially
+- Keep RCA brief and easy to understand
+- NO code snippets in final RCA - use plain language only
+- Always identify: What happened, Why, How to fix
+- Compare with how similar problems are solved in codebase
+- ALWAYS get user approval before posting to Linear
+- Include file:line references only in the exploration phase, not in final RCA
+- Implementation is optional - respect if the user only wants analysis
+- Always enter plan mode before implementing to get user approval on the approach
+
+## Common Mistakes to Avoid
+
+1. **Asking in text instead of using tool**: Writing "Would you like me to post?" is WRONG. You must call `AskUserQuestion` tool.
+2. **Returning after RCA generation**: Your task is NOT complete after Phase 5. You must continue to Phase 6 and 7.
+3. **Sequential exploration**: Launching explore agents one at a time. Use parallel tool calls in a single message.
+4. **Including code in RCA**: The final RCA should be plain language only.
+
+## Linear API Reference
+
+**Fetch issue:**
+```
+POST https://api.linear.app/graphql
+Query: issues(filter: { number: { eq: N }, team: { key: { eq: "TEAM" } } })
+Fields: id, identifier, title, description, state, assignee, labels, comments
+```
+
+**Post comment:**
+```
+POST https://api.linear.app/graphql
+Mutation: commentCreate(input: { issueId: "UUID", body: "content" })
+```
diff --git a/.claude/code-quality-rules.json b/.claude/code-quality-rules.json
new file mode 100644
index 00000000000..08ae1910f79
--- /dev/null
+++ b/.claude/code-quality-rules.json
@@ -0,0 +1,251 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "description": "Code quality rules, architecture patterns, and anti-patterns for Atlas Metastore skills",
+ "version": "1.0.0",
+
+ "layers": {
+ "rest": {
+ "pathPatterns": ["**/web/rest/**"],
+ "requiredPatterns": {
+ "@Timed": "All REST endpoint methods must have @Timed annotation",
+ "AtlasPerfTracer": "All REST methods must use AtlasPerfTracer in try-finally",
+ "validateQueryParamLength": "All path/query parameters must be validated",
+ "verifyAccess": "All entity operations must check authorization"
+ },
+ "forbiddenPatterns": {
+ "graph.traversal": "REST layer must not access graph directly",
+ "AtlasVertex": "REST layer must not manipulate graph vertices",
+ "AtlasEdge": "REST layer must not manipulate graph edges"
+ }
+ },
+ "service": {
+ "pathPatterns": ["**/glossary/*Service*", "**/discovery/*Service*", "**/tasks/*Service*"],
+ "requiredPatterns": {
+ "@GraphTransaction": "Service mutation methods should manage transactions"
+ },
+ "forbiddenPatterns": {
+ "AtlasVertex": "Service layer should delegate graph ops to stores"
+ }
+ },
+ "store": {
+ "pathPatterns": ["**/store/graph/v2/**"],
+ "requiredPatterns": {
+ "@GraphTransaction": "Store mutation methods must have @GraphTransaction"
+ },
+ "forbiddenPatterns": {
+ "HttpServletRequest": "Store layer must not access HTTP concerns",
+ "Servlets.": "Store layer must not use REST utilities"
+ }
+ },
+ "preprocessor": {
+ "pathPatterns": ["**/preprocessor/**"],
+ "requiredPatterns": {
+ "implements PreProcessor": "Preprocessors must implement PreProcessor interface",
+ "processAttributes": "Must override processAttributes method",
+ "EntityOperation.CREATE": "Must handle CREATE operation",
+ "EntityOperation.UPDATE": "Must handle UPDATE operation"
+ },
+ "forbiddenPatterns": {
+ "@GraphTransaction": "Preprocessors run within store transactions, must not declare own",
+ "HttpServletRequest": "Preprocessors must not access HTTP concerns",
+ "Kafka": "Preprocessors must not send notifications directly"
+ }
+ }
+ },
+
+ "preprocessorMappings": {
+ "AtlasGlossary": {
+ "preprocessor": "GlossaryPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/glossary/GlossaryPreProcessor.java",
+ "qnPattern": "{nanoId}"
+ },
+ "AtlasGlossaryTerm": {
+ "preprocessor": "TermPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/glossary/TermPreProcessor.java",
+ "qnPattern": "{nanoId}@{glossaryQN}"
+ },
+ "AtlasGlossaryCategory": {
+ "preprocessor": "CategoryPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/glossary/CategoryPreProcessor.java",
+ "qnPattern": "{parentPath}.{nanoId}@{glossaryQN}"
+ },
+ "DataDomain": {
+ "preprocessor": "DataDomainPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/datamesh/DataDomainPreProcessor.java",
+ "qnPattern": "default/domain/{nanoId}/super"
+ },
+ "DataProduct": {
+ "preprocessor": "DataProductPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/datamesh/DataProductPreProcessor.java",
+ "qnPattern": "{domainQN}/product/{nanoId}"
+ },
+ "Persona": {
+ "preprocessor": "PersonaPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/accesscontrol/PersonaPreProcessor.java",
+ "qnPattern": "{tenantId}/{nanoId}"
+ },
+ "Purpose": {
+ "preprocessor": "PurposePreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/accesscontrol/PurposePreProcessor.java",
+ "qnPattern": "{tenantId}/{nanoId}"
+ },
+ "AuthPolicy": {
+ "preprocessor": "AuthPolicyPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/AuthPolicyPreProcessor.java",
+ "qnPattern": "{parentEntityQN}/{nanoId}"
+ },
+ "Query": {
+ "preprocessor": "QueryPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/sql/QueryPreProcessor.java",
+ "qnPattern": "{collectionQN}/{userName}/{nanoId}"
+ },
+ "Collection": {
+ "preprocessor": "QueryCollectionPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/sql/QueryCollectionPreProcessor.java",
+ "qnPattern": "{userName}/{nanoId}"
+ },
+ "Folder": {
+ "preprocessor": "QueryFolderPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/sql/QueryFolderPreProcessor.java"
+ },
+ "Stakeholder": {
+ "preprocessor": "StakeholderPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/datamesh/StakeholderPreProcessor.java"
+ },
+ "StakeholderTitle": {
+ "preprocessor": "StakeholderTitlePreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/datamesh/StakeholderTitlePreProcessor.java"
+ },
+ "Asset": {
+ "preprocessor": "AssetPreProcessor",
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/AssetPreProcessor.java"
+ }
+ },
+
+ "highImpactFiles": [
+ {
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphMapper.java",
+ "impact": "ALL entity create/update operations",
+ "riskLevel": "CRITICAL"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/preprocessor/PreProcessorUtils.java",
+ "impact": "ALL preprocessors",
+ "riskLevel": "CRITICAL"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasEntityStoreV2.java",
+ "impact": "ALL entity store operations",
+ "riskLevel": "CRITICAL"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/type/AtlasTypeRegistry.java",
+ "impact": "ALL type resolution",
+ "riskLevel": "CRITICAL"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/RequestContext.java",
+ "impact": "ALL request processing (thread-local)",
+ "riskLevel": "CRITICAL"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasRelationshipStoreV2.java",
+ "impact": "ALL relationship operations",
+ "riskLevel": "HIGH"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/EntityGraphRetriever.java",
+ "impact": "ALL entity reads",
+ "riskLevel": "HIGH"
+ },
+ {
+ "path": "repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java",
+ "impact": "ALL typedef operations",
+ "riskLevel": "HIGH"
+ }
+ ],
+
+ "antiPatterns": [
+ {
+ "id": "generic-catch",
+ "pattern": "catch\\s*\\(\\s*Exception\\s",
+ "severity": "WARNING",
+ "message": "Catching generic Exception — use specific exception types (AtlasBaseException)",
+ "layers": ["all"]
+ },
+ {
+ "id": "empty-catch",
+ "pattern": "catch\\s*\\([^)]+\\)\\s*\\{\\s*\\}",
+ "severity": "CRITICAL",
+ "message": "Empty catch block — exceptions are silently swallowed",
+ "layers": ["all"]
+ },
+ {
+ "id": "system-out",
+ "pattern": "System\\.(out|err)\\.",
+ "severity": "WARNING",
+ "message": "Use SLF4J Logger instead of System.out/err",
+ "layers": ["all"]
+ },
+ {
+ "id": "thread-sleep",
+ "pattern": "Thread\\.sleep",
+ "severity": "WARNING",
+ "message": "Thread.sleep in production code — use event-driven patterns",
+ "layers": ["rest", "service", "store", "preprocessor"]
+ },
+ {
+ "id": "size-equals-zero",
+ "pattern": "\\.size\\(\\)\\s*==\\s*0",
+ "severity": "INFO",
+ "message": "Use CollectionUtils.isEmpty() instead of .size() == 0",
+ "layers": ["all"]
+ },
+ {
+ "id": "string-concat-log",
+ "pattern": "LOG\\.(info|warn|error|debug)\\([^)]*\\+[^)]*\\)",
+ "severity": "WARNING",
+ "message": "Use parameterized logging (LOG.info(\"msg: {}\", val)) instead of string concatenation",
+ "layers": ["all"]
+ },
+ {
+ "id": "mutable-static",
+ "pattern": "static\\s+(?!final)\\s*(Map|List|Set|Collection|HashMap|ArrayList)",
+ "severity": "WARNING",
+ "message": "Mutable static field — thread safety risk in multi-threaded server",
+ "layers": ["all"]
+ }
+ ],
+
+ "securityPatterns": {
+ "inputValidation": {
+ "required": ["Servlets.validateQueryParamLength"],
+ "description": "REST path/query parameters must be validated"
+ },
+ "ssiInjection": {
+ "pattern": "",
+ "description": "Server-Side Include tags must be detected and rejected in user input"
+ },
+ "authorization": {
+ "required": ["AtlasAuthorizationUtils.verifyAccess"],
+ "description": "Entity operations must verify authorization"
+ },
+ "logSanitization": {
+ "check": "User-controlled strings in log statements must sanitize newlines",
+ "fix": "replaceAll(\"[\\r\\n]\", \"_\")"
+ }
+ },
+
+ "testConventions": {
+ "framework": "TestNG",
+ "mockFramework": "Mockito",
+ "setupAnnotation": "@BeforeMethod",
+ "teardownAnnotation": "@AfterMethod",
+ "testAnnotation": "@Test",
+ "assertionPackage": "org.testng.Assert",
+ "requiredTeardown": ["RequestContext.clear()", "closeable.close()"],
+ "requiredSetup": ["MockitoAnnotations.openMocks(this)", "RequestContext.clear()"],
+ "testLocation": "repository/src/test/java/",
+ "integrationTestLocation": "webapp/src/test/java/org/apache/atlas/web/integration/"
+ }
+}
diff --git a/.claude/commands/atlas-api.md b/.claude/commands/atlas-api.md
new file mode 100644
index 00000000000..6419c230ff8
--- /dev/null
+++ b/.claude/commands/atlas-api.md
@@ -0,0 +1,30 @@
+Answer questions about Atlas Metastore API endpoints using the official API reference docs.
+
+## Instructions
+
+**Do NOT load all docs upfront.** Read only the file(s) relevant to the user's question:
+
+| If the question is about… | Read this file |
+|---|---|
+| Tag / classification desync, `__traitNames`, `__classificationNames`, `repairClassifications` | `docs/api/repair-endpoints.md` |
+| Index repair, JanusGraph, `repairindex`, missing from search, composite/single index | `docs/api/repair-endpoints.md` |
+| `hasLineage` flag wrong, lineage tab issues, `repairhaslineage` | `docs/api/repair-endpoints.md` |
+| Persona alias missing, ES alias, `accesscontrolAlias` | `docs/api/repair-endpoints.md` |
+| `outputPorts`, `repairattributes`, `qualifiedName` migration | `docs/api/repair-endpoints.md` |
+| Creating or updating entities, `entity/bulk`, relationships, classifications, business metadata | `docs/api/entity-bulk.md` |
+| Searching assets, `indexsearch`, DSL queries, pagination, aggregations, `relationAttributes` | `docs/api/search-indexsearch.md` |
+
+If the question spans multiple areas, read the relevant files for each area — but only those.
+
+After reading, answer using only the loaded content. Do not guess or invent endpoint signatures, params, or behaviour not present in the docs.
+
+## Usage
+
+`/atlas-api `
+
+**Examples:**
+- `/atlas-api how do I repair classifications for a list of GUIDs?`
+- `/atlas-api what privilege is needed to call repairindex?`
+- `/atlas-api show me a bulk entity update with business attributes`
+- `/atlas-api how do I paginate indexsearch results?`
+- `/atlas-api what params does repairAllClassifications accept?`
diff --git a/.claude/commands/atlas-app-logs.md b/.claude/commands/atlas-app-logs.md
new file mode 100644
index 00000000000..e813701867f
--- /dev/null
+++ b/.claude/commands/atlas-app-logs.md
@@ -0,0 +1,114 @@
+Query Atlas metastore application logs from ClickHouse (otel_logs) using the Grafana ClickHouse MCP.
+
+## Usage
+`/atlas-app-logs [tenant=] [from=