entity

Object Class

The canonical kind of building object BuildPlan can classify, specify, and eventually build. Aggregate root of a single-parent tree, cross-referenced to Uniformat, Masterformat, ObjectClass codes, and one Revit category.

Entity(Aggregate Root)Status: Implemented in api-v1Aggregate Root

Purpose

An object class answers what kind of thing is this? It is not a product, a product model, or a Revit category — it is the classification node those things hang off. Classes carry the external classification cross-references (Uniformat, Masterformat, OmniClass, ObjectClass code), at most one Revit category link, and the parameter assignments that make a class specifiable.

The class graph is the spine of the module: ClassParameter assignments attach parameters to it, Revit category scope is resolved from it, and product-marketplace assigns a class to a product rather than defining its own taxonomy.

Lifecycle

draft → approved → archived, from OBJECT_CLASS_STATUSES.

TransitionRule
draft → approvedSteward approve. Gated — see below
draft → archivedAllowed
approved → archivedAllowed
approved → draftDemotion only, via demoteToDraft(). Clearing a required unique classification with no remaining ancestor value demotes the class
archived → draftreinstate() only. Archived has no forward transition

approved is the only usable tier (isUsableObjectClassStatus), and the only visible one when includeInactive is false. A new root is authored in draft (createDraftRoot).

Approve gate. Approving requires all four of Uniformat, Masterformat, ObjectClass code, and Revit category (REQUIRED_OBJECT_CLASS_APPROVE_KINDS) to be present — resolved along the ancestry, so an inherited value satisfies the gate exactly as a directly authored one does. It additionally requires every ancestor to already be approved (assertApprovedAncestorChain, ADR-0050); the error names the offending ancestor and tells the steward to approve it first. ApproveObjectClassUseCase runs the ancestor check before the classification check and is idempotent on an already-approved class.

Invariants

  • Exactly one parent, or none (ADR-0046). A class may not be its own parent or its own child, and the graph is acyclic.
  • name is required and non-empty.
  • At most one authored Revit category link. More than one on rehydrate is a domain error, not a silent pick.
  • A class may be approved only if every ancestor on its single path to the root is approved.
  • needs_review requires a reviewReason.
  • When the Revit category status is not current, the projected bindable ids are null — a stale or needs_review link never hands out a category to bind against.
  • depth, path, is_leaf, and root_object_class are derived. They are recomputed by the ObjectClassParentGraphChanged hierarchy handler over the seeded subtree, never authored and never a source of truth.

root_object_class

root_object_class is the parentless ancestor at the top of a class’s lineage, derived and persisted on every row. It is explicitly not an input to Revit category resolution — that was a DAG-era rule retired with ADR-0046. Revit applicability comes from the effective assignment set, not from which tree a class happens to sit in.

Classification fill (ADR-0050)

Stewards author *_direct values only. Everything else is inferred:

  • Make-unique is inferred, never authored. On the edited node, a direct value that differs from the value at the nearest make-unique ancestor becomes the new make-unique source; clearing it unsets make-unique and the node inherits again.
  • Fill runs top-down. Walk up to the nearest make-unique ancestor, then fill that value down until the next one.
  • Effective mirrors materialized *_direct. Because the fill is materialized, no read-time ancestry walk happens. *_inherited is always cleared under this rule.
  • Kinds are independent. Uniformat, Masterformat, ObjectClass code, and Revit category each fill separately. OmniClass is a flat, non-propagating link and is not part of the fill or the approve gate.
  • More than one direct link for a kind is recorded as a MULTIPLE_DIRECT_CLASSIFICATIONS violation rather than resolved by guessing.

Revit category binding status

The link carries a health status — current, stale, needs_review, unresolved — and a provenance source of authored or inherited.

This is not a lifecycle. It says nothing about whether the class is approved and it never gates approve or archive on its own; it describes whether the class’s category link is currently trustworthy. Two words that look similar are also distinct: this status describes an object class classifying as a Revit category, whereas Revit category scope describes what a Revit project binding covers.

Domain Events

  • ObjectClassParentGraphChanged — published after parent tree edges change (move, merge, copy, delete, add-child, create-root), carrying affectedObjectClassIds and changedAt. Four handlers are registered against it: Revit category reconciliation, hierarchy field recomputation, classification fill with demote-on-lost-classification, and rendered parameter template sync. Ids in the payload are de-duplicated.

No other object-class domain event exists — lifecycle transitions are persisted directly by the lifecycle use cases.

OpenAPI Operations

ADRs

The platform series lives in docs/adr/07_Architectural_Decision_Records.md. The per-file series is separate and the numbers collide — see docs/adr/README.md.

ADRDecision
Platform ADR-0046The object-class hierarchy is a single-parent tree; no primary-parent device
Platform ADR-0048domain_id is the public identifier; never a rec… id in a payload
Platform ADR-0050Object-class lifecycle plus the four-kind classification approve gate; inherit-by-default fill with make-unique
Platform ADR-0052active is displayable-but-unvetted and belongs to ParameterKey alone — which is why this class has no active tier