---
name: catalyst-schema
description: >
  Aurora YAML schema analysis and editing. Validates field names, descriptions,
  types, and module semantics following DDD best practices. Trigger: When
  analyzing or editing *.aurora.yaml files, improving field naming, adding
  descriptions, or validating schema semantics.
license: MIT
metadata:
  author: aurora
  version: '1.1'
  auto_invoke:
    'Analyzing or editing *.aurora.yaml files, schema validation, field
    semantics'
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
---

## When to Use

Use this skill when:

- Analyzing `*.aurora.yaml` files for quality and consistency
- Editing YAML schemas (creating, updating, or deleting fields)
- Validating field naming conventions and descriptions
- Ensuring module descriptions explain purpose and context
- Reviewing data type appropriateness

**Always combine with:**

- `catalyst-cli` skill when regenerating after YAML changes
- `aurora-project-structure` skill for locating YAML files
- `conventional-commits` skill when committing schema changes

## Detailed References

- [Types & Varchar Standards](types-reference.md) — Type selection guide, byte-optimized varchar lengths
- [Patterns & Workflow](patterns-workflow.md) — Common patterns, analysis/editing workflows, change log template

---

## Critical Patterns

### Module Description (REQUIRED)

Every `*.aurora.yaml` must have a `description` property **before** `aggregateProperties:`.

```yaml
# ✅ CORRECT
version: 0.0.1
boundedContextName: iam
moduleName: permission
description: >
  Module containing the permissions associated with each bounded context, to be
  used to manage access to each API.
aggregateProperties:
  - name: id
```

**Description should explain:** What the module contains, what it's used for, how it relates to other modules.

---

### Mandatory Fields (REQUIRED in all modules)

**All modules MUST include these fields:**

1. `rowId` (after `id`) — `bigint`, autoIncrement, unique
2. `createdAt` (end) — `timestamp`, nullable
3. `updatedAt` (end) — `timestamp`, nullable
4. `deletedAt` (end) — `timestamp`, nullable

**Field order:** `id` → `rowId` → ... module fields ... → `createdAt` → `updatedAt` → `deletedAt`

---

### Field Naming Conventions

| Pattern               | Use For             | Examples                                |
| --------------------- | ------------------- | --------------------------------------- |
| `camelCase`           | All field names     | `firstName`, `orderDate`, `totalAmount` |
| `is*`, `has*`, `can*` | Boolean flags       | `isActive`, `hasChildren`, `canEdit`    |
| `*At`                 | Timestamps          | `createdAt`, `updatedAt`, `publishedAt` |
| `*Date`               | Date-only fields    | `birthDate`, `startDate`, `endDate`     |
| `*Id`                 | Foreign keys        | `authorId`, `categoryId`, `parentId`    |
| `sort`                | Display/UI ordering | `sort` (NOT `displayOrder`, `order`)    |

---

### Field Descriptions (MANDATORY)

**Every field MUST have a description that explains WHY, not WHAT:**

```yaml
# ❌ BAD
- name: price
  type: decimal
  description: The price of the book

# ✅ GOOD
- name: price
  type: decimal
  decimals: [10, 2]
  description: >
    Retail price in the store's base currency. Does not include taxes or
    discounts. Used as base for price calculations.
```

---

### ID Fields (CRITICAL RULE)

**Fields of type `id` MUST NOT have a `length` property.**

```yaml
# ✅ CORRECT
- name: id
  type: id
  primaryKey: true

# ❌ INCORRECT
- name: id
  type: id
  length: 36  # ← DELETE THIS
```

---

### Relationship Fields (CRITICAL RULE)

| Side                      | Has FK? | Use                                      |
| ------------------------- | ------- | ---------------------------------------- |
| Child/Many (invoice-line) | YES     | `type: id` + `relationship` block inside |
| Parent/One (invoice)      | NO      | `type: relationship` (navigation only)   |
| Many-to-many              | NO      | `type: relationship` + `pivot` config    |

**⚠️ NEVER define both `invoiceId` (type: id) AND `invoice` (type: relationship) in the SAME module.**

```yaml
# ✅ child.aurora.yaml - ONLY the FK field
- name: parentId
  type: id
  relationship:
    type: many-to-one
    field: parent
    aggregateName: MyParent
    modulePath: my-context/parent

# ✅ parent.aurora.yaml - ONLY the navigation property
- name: children
  type: relationship
  relationship:
    type: one-to-many
    aggregateName: MyChild
    modulePath: my-context/child
    key: parentId
```

---

### Web Component — `displayFields` (for `grid-select-element`)

When a field uses `webComponent.type: grid-select-element`, define `displayFields` to control
which fields from the related entity are shown in the readonly input.

```yaml
# ✅ CORRECT — displayFields defined
- name: meterId
  type: id
  webComponent:
    type: grid-select-element
    displayFields:
      - model
      - serialNumber
  relationship:
    type: many-to-one
    aggregateName: IsuMeter
    modulePath: isu/meter
    key: id
    field: meter
```

**Rendering rules:**

- 1 field → `selected()?.field1`
- 2+ fields → `selected() ? selected().field1 + ' (' + selected().field2 + ')' : ''`
- Not defined → falls back to `?.name` (backward compatible)

**`displayFields` values MUST be valid field names from the related module.**

---

### Cross-Module Consistency

Use the same field names across ALL modules:

```yaml
- name: id        # Not: ID, _id, uuid
- name: createdAt # Not: created, createdDate
- name: updatedAt # Not: updated, modifiedAt
- name: deletedAt # Not: deleted, removedAt
- name: isActive  # Not: active, enabled
```

---

### Index Names (63-char limit) — MANDATORY VALIDATION

PostgreSQL limits index names to **63 characters**. Aurora generates: `{boundedContext}_{module}_{fieldName}` (snake_case).

**Every time you add `index: index` or `index: unique`, calculate the generated index name length.**

```
index_name = snake_case(boundedContextName) + "_" + snake_case(moduleName) + "_" + snake_case(fieldName)
```

**If length > 63 → MUST add `indexName` property with abbreviated name.**

```yaml
- name: administrativeAreaLevel1Id
  type: id
  index: index
  indexName: bpp_partner_addr_admin_area_lvl1_id  # < 63 chars
```

**Abbreviation pattern:** `{BC_abbrev}_{short_module}_{short_field}`

| Bounded Context           | Abbrev |
| ------------------------- | ------ |
| `production-planning`     | `pp`   |
| `business-partner-portal` | `bpp`  |
| `common`                  | `cmn`  |

---

## Decision Trees

### What Type Should This Field Be?

```
Is it a UUID identifier? ────YES───> type: id (NO length!)
      │
      NO
      │
Is it true/false? ────YES───> type: boolean (use is*/has*/can* prefix)
      │
      NO
      │
Is it money? ────YES───> type: decimal with decimals: [12, 2]
      │
      NO
      │
Fixed set of options? ────YES───> type: enum with enumOptions
      │
      NO
      │
Date and time? ────YES───> type: timestamp
      │
      NO
      │
Short text (< 255 chars)? ────YES───> type: varchar with maxLength
      │
      NO
      │
Long text? ────YES───> type: text
```

### Should I Edit or Just Analyze?

```
User explicitly requested edit? ────YES───> Edit mode
      │
      NO → Analysis mode (generate report)
```

---

## Anti-Patterns to Avoid

| ❌ Don't                                  | ✅ Do                                             |
| ----------------------------------------- | ------------------------------------------------- |
| Skip module description                   | Always add description before aggregateProperties |
| Skip mandatory fields (rowId, timestamps) | Always include rowId, createdAt, updatedAt, deletedAt |
| Use abbreviations (dt, qty, amt)          | Use full words (createdAt, quantity, amount)       |
| Name booleans without prefix (active)     | Use semantic prefix (isActive, hasPermission)     |
| Add `length` to `id` type fields          | Never specify length for id type                  |
| Write "The price" as description          | Explain context: "Retail price in base currency..." |
| Use `float` for money                     | Always use `decimal` with proper scale            |
| Duplicate relationship definitions        | FK side uses `type: id`, inverse uses `type: relationship` |

---

## Resources

- **Aurora Docs**: Check `aurora-cli` skill for regeneration commands
- **Project Structure**: Use `aurora-project-structure` skill to locate YAMLs

## Related Skills

| Skill                      | When to Use Together                                          |
| -------------------------- | ------------------------------------------------------------- |
| `aurora-cli`               | After editing YAML, regenerate with `aurora load back module` |
| `aurora-project-structure` | To locate YAML files in correct directories                   |
| `conventional-commits`     | When committing schema changes                                |
| `aurora-cqrs`              | Understanding how YAML generates commands/queries             |
