Amends 0096: the trait list of its decision 4 gains the
inventory of decision 2 below, and the owner named here applies where the two differ. The note on
0096 records which of its rows stand.
Context
0096 set the grammar for trait names and a first table of owners on 2026-10-19. Later decisions moved some traits: SEO metadata to@stackra/seo, the package that 0301
creates for sitemaps, robots rules and alternates, commenting to the collaboration package (0225),
and 0116 bundles the content traits into content types. A model author needs
one list of every trait a model can carry, with the package that owns each one. A reviewer also needs to know
what is deliberately not a trait, because the same wish (personal data, consent, a feature gate) keeps coming
back as one. The owner settled both in the final structure review of 2026-10-24.
Decision
-
Grammar and owners. Traits keep the forms of 0096 decision 2:
Has*for a trait that owns data, and*ablefor a capability that other code acts on. Each trait is exported by the package that owns its behaviour, and that owner is recorded in the trait registry of@stackra/model. -
The inventory. These are the model traits of this inventory, each with its owner. A trait of the 0096 table that this list does not name stands under the note on 0096 until the owner decides it.
-
Four names do not take a grammar form of 0096.
Timestamps,Versioned,AppendOnlyandEffectivekeep the names that the inventory gives them.SoftDeletesstays by 0096 decision 3. A rename is a later decision, and it changes this table and the trait registry together. -
Seven traits are new to the list.
HasMetadataaddsmetadata jsonb not null default '{}'. Its keys are strings, and a record holds at most 16 KB of it. It never holds a secret or classified data, and gen checks that. It is filterable only when the model declares a GIN index on it. It is on by default for every model that has a Resource or a public API, and it is opt-in for every other model. A typed value that the tenant defines stays a custom field (0116), and metadata never carries code or logic (0154).Taggableholds tags and labels, drawn from a tag list that each tenant keeps.HasOwnerholds the owner or assignee of a record. It serves “my records”, assignment and owner-based policy.HasTreeholds a hierarchy as anltreepath, through the approved extension. Categories and organisation units use it.Effectiveholds a validity window, withvalid_fromandvalid_until. Prices, contracts and schedules use it.HasLocationis defined by ADR 0314, which is not on disk yet. It is listed here so the inventory is complete.Schedulableis decision 6.
-
What is not a trait.
- Personal data. A field carries
@Classify(0134).HasEncryptedFieldsencrypts the named fields at the application layer, as 0046 first described it; 0134 superseded 0046 and does not decide this trait. 0134 decision 5 encrypts PII in audit diffs with the tenant key. WhetherHasEncryptedFieldsuses that key, and how its keys are managed, is not decided here. - Consent. Consent is a record of its own, in the privacy and identity module. It holds the purpose, the policy
version, the granted and withdrawn times, the source and the proof, because consent needs a history and evidence.
A model links to it with
Link<T>(0104). Cookie and analytics consent stays a separate matter:@stackra/analyticsgates it under the rules of 0298. - Entitlements. An entitlement gates a feature, not a record, so there is no trait for it. Actions, routes and
resources take the
entitlement:option. - Decorators.
@Auditable,@Searchableand@AccessLoggedcarry metadata only. They are not traits and do not appear in the inventory.
- Personal data. A field carries
-
Schedulable. A scheduled update is a future revision of a record, built on
HasRevisions.@stackra/schedulerapplies the revision at its time, as a tenant schedule of 0148 decision 5. It is not a recurring@Cronjob, which 0078 decision 6 keeps for recurring work. A scheduled update can be previewed and rolled back. A campaign groups several scheduled updates.Effectivestays for validity windows, which are a different thing.Schedulableis built after the core data packages.
Consequences
- One table answers who owns a trait. Three owners move from the 0096 table:
HasSeoto@stackra/seo,Commentableto@stackra/collaboration, andTranslatableto@stackra/model, where 0162 places the decorator. - A resource model gets a
metadatacolumn by default. Each of its records may hold up to 16 KB of it, and the GIN index is an explicit choice for each filter. - A trait that a model does not declare adds no column, so a model stays as small as its traits.
- Four names are not in the grammar form (decision 3). They are kept so that the list matches the owner’s decision.
- Panel additions decided at the same time, which this ADR records and does not schedule: a built-in clone (duplicate) action on resources, and a record activity tab that puts audit, comments and domain events on one timeline. Both belong to the resource surface of 0307.
Enforcement
- planned — the grammar check and the trait-list test of 0096, which are not written yet.
- planned — the gen check of
HasMetadata(no secret or classified data, the 16 KB cap, and a GIN index before filtering). - No check exists today for the owner column or for
Schedulable.