> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 0096 — Trait naming grammar and the trait list

> Accepted, amended by 0133, 0162 · 2026-10-19

> [!NOTE]
>
> Amended by [0225](/adr/0225-realtime-drivers-co-editing-and-the-commentable-trait): the
> Commentable trait.

> [!NOTE]
>
> Amended by [0162](/adr/0162-validation-translatable-fields-and-entityschema-placement): any model
> can have translatable fields through the @Translatable decorator.

> [!NOTE]
>
> Amended by [0133](/adr/0133-field-semantics-model-keys-rename-safe-fields-and-lifecycle-events):
> the HasUserStamps trait joins the trait table.

## Context

Models opt into behaviour such as timestamps, delete policy, audit, search and encryption
([0046](/adr/0046-global-data-invariants-and-per-model-opt-ins)). Each package that adds behaviour to a model or another class
needs a name for it that tells a reader what it does. Laravel's mix of traits and "concerns" with inconsistent names is the
example to avoid.

## Decision

1. **Reusable class behaviour is a trait**, created with `createTrait` from `@stackra/model`. There are no "concerns".

2. **Names follow one grammar**:

   | Form | Means | Example |
   | - | - | - |
   | `Has*` | The model owns data (columns or relations) | `HasTimestamps` |
   | `*able` | A capability that other code acts on | `Searchable` |
   | `As*` | The class plays a role for another package | `AsTenant` |
   | `InteractsWith*` | Helpers on a class that is not a model | `InteractsWithQueue` |

3. **`SoftDeletes` is the one exception**, kept for recognition.

4. **The trait list**, each owned by one package:

   | Trait | Owning package | Purpose |
   | - | - | - |
   | `HasTimestamps` | `@stackra/model` | Created and updated times |
   | `HasFactory` | `@stackra/database` | Test and seed factories |
   | `SoftDeletes` | `@stackra/database` | Delete policy `soft` |
   | `Archivable` | `@stackra/database` | Delete policy `archive` |
   | `Prunable` | `@stackra/database` | Scheduled pruning |
   | `Versionable` | `@stackra/database` | Row version for concurrency or history |
   | `HasEncryptedFields` | `@stackra/database` | Application-layer PII field encryption |
   | `HasTenant` | `@stackra/tenancy` | `tenant_id` with row-level security |
   | `AsTenant` | `@stackra/tenancy` | The model is the tenant root |
   | `AsSubject` | `@stackra/iam` | The model can be a `check()` subject |
   | `Auditable` | `@stackra/outbox` | Changes publish audit events ([0047](/adr/0047-audit-service-via-events)) |
   | `Searchable` | `@stackra/search` | Indexed through queue jobs ([0089](/adr/0089-search-fresh-with-pro-engine-drivers)) |
   | `HasMedia` | `@stackra/media` | Attached files |
   | `Notifiable` | `@stackra/notifications` | Can receive notifications |
   | `HasSettings` | `@stackra/settings` | Scoped settings |
   | `InteractsWithQueue` | `@stackra/queue` | Release, retry and fail helpers on jobs |
   | `HasRevisions` | `@stackra/content` | Draft and published revisions ([0116](/adr/0116-content-types-and-custom-fields)) |
   | `Publishable` | `@stackra/content` | Draft, scheduled and published states |
   | `Sluggable` | `@stackra/content` | A unique slug per locale |
   | `Translatable` | `@stackra/content` | Per-locale field values |
   | `HasSeo` | `@stackra/content` | SEO metadata for the record |
   | `HasCustomFields` | `@stackra/content` | Tenant-defined fields in a side table, bounded by entitlements |

5. **A new trait is added to this list** in the change that creates it.

## Consequences

* A trait's name tells a reader whether it adds columns, exposes a capability, or adapts a role.
* `@stackra/model` owns the trait registry, so traits from many packages compose on one class.
* Which package owns `Auditable` is the least certain row: audit is a service fed by events, and the trait only publishes.

## Enforcement

Planned: a lint rule that every `createTrait` call's name matches the grammar or is `SoftDeletes`, and a test that every trait
is listed in this page's table. Nothing checks this today.

## Supersedes and sources

Supersedes: none. Sources: recommendation by the coordinating agent, 2026-10-19, accepted by the owner in Round 10; the
content traits come from the owner's CT2 and CT3 decisions in Round 11. The list is derived from
[0046](/adr/0046-global-data-invariants-and-per-model-opt-ins) and [0084](/adr/0084-figentra-os-package-port-plan).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.