# CODEOWNERS-Based Team Assignment

## How It Works

When you upload a CODEOWNERS file, Luciq maps your crash reports to the right team automatically based on where the crash happened in your code.

Here's what happens under the hood:

1. **Team Mapping**: Team names from your CODEOWNERS file are matched to your existing teams on the Luciq dashboard. This matching is flexible since it's case-insensitive and treats hyphens, underscores, and spaces as the same thing. So `@org/my-team` will match a team called "My Team" without any extra configuration.
2. **Frame Selection**: When a crash comes in, Luciq looks at the crashed thread and picks the **top application frame** (the first frame that belongs to your code, not system libraries). The file path from that frame is what gets used for team matching.
3. **Path Matching**: That file path is compared against the paths in your CODEOWNERS rules. The matching works on path segments (split by `/`), so a rule for `LibraryA` will match any crash in `LibraryA/SomeFile.swift`, `LibraryA/SubDir/AnotherFile.swift`, etc.
4. **Specificity-Based Resolution**: When multiple rules match the same crash, Luciq picks the **most specific** one. Specificity is determined by the number of path segments in the matching rule — more segments means a more specific match. See [Specificity-Based Matching](#specificity-based-matching) below for details.

## CODEOWNERS File Example

A CODEOWNERS file lives in your repository and defines which team owns which part of the codebase. Here's what a typical one looks like:

```
# Each line maps a path pattern to a team
# The format is: <path>    <team>

# Networking layer owned by the platform team
/Sources/Networking/                    @org/platform-team

# Authentication module owned by the identity team
/Sources/Auth/                          @org/identity-team

# Checkout and payments owned by the commerce team
/Sources/Checkout/                      @org/commerce-team
/Sources/Payments/                      @org/commerce-team

# Everything under the analytics module
/Sources/Analytics/                     @org/data-team
```

With this file uploaded, if a crash happens in `Sources/Checkout/CartViewController.swift`, Luciq will automatically assign it to **Commerce Team**. A crash in `Sources/Auth/LoginManager.swift` goes to **Identity Team**, and so on.

A team can own multiple paths (like the commerce team above owning both Checkout and Payments), and paths can be as broad as a top-level directory or as specific as a single file.

{% hint style="warning" %}
**Don't Mix Files and Directories for the Same Team**

When defining ownership rules for the same team, avoid mixing file paths and directory paths. If a team has both file-level and directory-level rules, only the file-level rules will be used for assignment.

**Workaround**: Use only directories or only files for the same team — don't mix both.
{% endhint %}

## Specificity-Based Matching

When multiple ownership rules match the same crash, Luciq uses **specificity** to determine which team gets assigned. The rule with the most path segments wins.

### How Specificity Is Calculated

Specificity is based on the number of segments in the matching path token, where segments are separated by `/` (for file paths) or `.` (for Android packages).

| Rule token                     | Segments | Specificity |
| ------------------------------ | -------- | ----------- |
| `Sources`                      | 1        | 1           |
| `Sources/Networking`           | 2        | 2           |
| `Sources/Networking/WebSocket` | 3        | 3           |

### Example

Consider the following ownership definitions:

| Team           | Rule                                        |
| -------------- | ------------------------------------------- |
| Platform Team  | Path matches `Sources/Networking`           |
| Real-Time Team | Path matches `Sources/Networking/WebSocket` |

If a crash occurs in `Sources/Networking/WebSocket/ConnectionManager.swift`:

* **Platform Team** matches with specificity **2** (`Sources/Networking` has 2 segments)
* **Real-Time Team** matches with specificity **3** (`Sources/Networking/WebSocket` has 3 segments)

**Result**: The crash is assigned to **Real-Time Team** because it has the more specific rule.

### Tie-Breaking

When two rules have the **same specificity** (same number of path segments), the rule that was **most recently updated** wins. This ensures that if you update a definition, it takes priority over older definitions with the same level of specificity.

### How It Works Across Platforms

Specificity-based matching applies across all platforms:

| Platform         | Field             | Separator | Example                             |
| ---------------- | ----------------- | --------- | ----------------------------------- |
| **iOS**          | Path              | `/`       | `Controllers/Auth` → 2 segments     |
| **Android**      | Package           | `.`       | `com.example.app.auth` → 4 segments |
| **Flutter**      | Flutter Path      | `/`       | `lib/features/auth` → 3 segments    |
| **React Native** | React Native Path | `/`       | `src/screens/Auth` → 3 segments     |

For **filename-based** rules (as opposed to path/package), the specificity is always **1** regardless of the filename. This means path-based rules will generally take precedence over filename-based rules when both match.

## How to Test

To verify that team assignment is working correctly for your CODEOWNERS setup, keep in mind that Luciq needs **server-side symbolication** to extract file paths from crash frames, and that only happens when dSYMs are available.

Here's what you need to do:

1. **Build in Release mode**: Debug builds don't generate dSYM files. You need to archive or build your app in Release configuration for Xcode to produce them.
2. **Upload your dSYMs**: Once you have the dSYM files, upload them to Luciq (either manually through the dashboard or via your CI/CD pipeline). Without dSYMs, Luciq can't resolve the file paths needed for team matching.
3. **Trigger a crash**: Run the Release build, trigger a crash, and relaunch the app so the crash report gets sent.

{% hint style="warning" %}
**SDK-Side Symbolication**

If the SDK sends crashes that are already symbolicated (i.e., the stack frames already contain file paths and line numbers from the SDK side), server-side symbolication is skipped, and team assignment will not happen. This is why testing with a proper Release build and uploaded dSYMs is the correct flow.
{% endhint %}

Once the crash appears on your dashboard, you should see it assigned to the expected team based on your CODEOWNERS rules.
