Resources

Entities and Properties

How to add typed business-data definitions to a SpecScore repository.

Frozen. The entity and property Doc-Kinds are feature-frozen per decision 0003 β€” One Structural Language: existing files, lint rules, and the shipped CLI support (specscore entity …, specscore property … in specscore/specscore-cli) remain supported unchanged, but the Doc-Kinds accept no new capabilities. New structural modelling work uses ModelSpec; a legacy-to-ModelSpec migration path will be specified before formal deprecation. This page remains the author walkthrough for repositories that already use the frozen Doc-Kinds. Authoritative specs: entity, property.

Two Doc-Kinds, One Pair

SpecScore historically only described behavior. Entities and properties add a typed data layer:

Doc-Kind One-line job File pattern
property Reusable single-field definition (email, iso-currency-code) <slug>.property.md
entity Typed business object that bundles properties (User, Order) <slug>.entity.md

Both Doc-Kinds live anywhere under spec/features/** β€” typically next to the feature or module that owns them. There is no central spec/entities/ directory. See the property Feature's location rule and the matching entity Feature rule.

The Big Idea: Frontmatter is the Source of Truth

Unlike every other SpecScore Doc-Kind (feature, idea, plan, scenario, task), an entity or property file carries its structured data in YAML frontmatter, not in Markdown prose. The body is human commentary on the frontmatter, plus two machine-maintained sections.

This is a deliberate departure. The reason: every YAML parser in every language can consume the definition directly, without parsing Markdown tables. That makes downstream tooling β€” validators, code generators, third-party UI surfaces over the spec graph β€” viable on top of SpecScore.

A worked example: the email property lives in this repo as a smoke-test fixture. Its full source of truth is:

---
kind: property
id: email
data_type: string
description: An RFC 5322 email address used to identify the human or service owner of a SpecScore artifact.
checks:
  required: true
  max_length: 320
  pattern: "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+{{content}}quot;
  trim: true
  lowercase: true
---

That YAML is consumed by lint, by specscore property refs email (once shipped), and by anything else that wants to know what email means. The body's ## Description is for humans; the body's ## Referenced by is regenerated by specscore lint --fix from a fresh scan of every entity in the repo.

Walkthrough: Add an Entity that Reuses a Property

Suppose you want to add a User entity that has an email field, and you already have a shared email.property.md file. The entity references the property β€” no copy-paste of the email checks.

The full source of the smoke-test fixture in this repo, spec/features/idea/user.entity.md:

---
kind: entity
id: user
singular: User
plural: Users
description: The human or service identity that owns a SpecScore artifact.
properties:
  - name: id
    data_type: string
    description: Stable identifier.
    checks:
      required: true
      min_length: 1
      max_length: 64
      pattern: "^[a-zA-Z0-9_\\-.@]+{{content}}quot;
  - name: email
    ref: ./email.property.md
  - name: display_name
    data_type: string
    checks:
      required: false
      max_length: 80
      trim: true
  - name: created_at
    data_type: datetime
    checks:
      required: false
---

Two things to notice:

The body of the entity follows the same managed-section pattern as the property file β€” a hand-written ## Description, then a managed ## Properties table that specscore lint --fix renders from the frontmatter, then a managed ## Referenced by section that lists every other entity that inherits from this one. See the entity Feature's body-shape rules for the canonical contract.

Inheritance: Additive Only

If one entity logically extends another, declare it in frontmatter:

---
kind: entity
id: admin-user
singular: AdminUser
plural: AdminUsers
inherits: ./user.entity.md
properties:
  - name: permissions
    data_type: array
    checks:
      required: true
      items:
        data_type: string
---

AdminUser carries every property of User plus the new permissions field. Lint enforces that the child does NOT redefine, tighten, or remove any inherited property β€” additive only in MVP. Override semantics are a tar pit; they're deferred until real reuse patterns demand them. See the inheritance rule and the no-cycles rule.

Managed Sections

Two body sections in an entity, and one in a property, are managed views over the frontmatter and the repo scan respectively. They are wrapped in canonical markers:

## Properties

<!-- managed-by: specscore lint --fix -->
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `id` | string | yes | Stable identifier. |
| `email` | string *(via [email](./email.property.md))* | yes | An RFC 5322 email address. |
…
<!-- end-managed -->

Rules:

See the managed-section requirements for the canonical contract.

When to Use What

You want to… Use
Define a single reusable field (email, iso-currency-code, slug) A standalone Property file
Define a bag of fields that describes a business object (User, Order, LineItem) An Entity file
Reuse the same field shape on three different entities Define it once as a Property; ref: it from each entity
One-off field used by exactly one entity Inline the field inside the entity's properties list
Extend an entity that already exists Add inherits: to the child; only append new properties

What's Not in MVP

Named explicitly so you don't trip over a missing feature:

See the source Idea's Not Doing list for the full set with rationale.

See Also

Outstanding Questions

None at this time.

View as Markdown