entity

Class Parameter

The assignment of one ParameterKey to one ObjectClass, with the local rules governing it. Always called an assignment — binding is reserved for Revit placement.

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

Purpose

A class parameter is the assignment of one ParameterKey to one ObjectClass, carrying the rules that apply locally: whether the parameter is required, how far down the tree it reaches, and which values are allowed at that class.

The word is assignment, never binding. Binding is reserved for Revit placement — project binding, family application, binding kind — and using it here loses that distinction. The aggregate still has method names from before the rename (bind, reviseBinding), and the resolver still calls a selected assignment a “binding” internally; the vocabulary of the contract and of this catalog is assignment.

No lifecycle

A class parameter has no lifecycle. It exists or it does not.

AssignmentOrigin looks like a status and is not one — it is provenance:

OriginMeaning
directAuthored on this class. The only origin that accepts deletion and allowed-value edits
renderedA materialized overlay produced from an ancestor’s assignment
legacy_unknownProvenance was never recorded. The default when rehydrating a row that has no origin

AssignmentOrigin is server-owned and hidden from stewards: it is set by the server on create, on rehydrate, and by the promote paths (promoteToDirect, promoteToDirectAndReviseBinding), and it is not something a steward chooses in the Airtable UI.

Invariants

  • One assignment pairs exactly one object class with exactly one parameter key.
  • Origin fields are mutually exclusive (assertOriginFields, INVALID_CLASS_PARAMETER_ORIGIN_FIELDS): a direct row must not carry a child behavior, and a rendered row must not carry an inheritance mode. This is a domain invariant checked on create, on rehydrate, and on revise — not a contract-layer nicety. Only legacy_unknown rows may carry both.
  • Allowed values must be unique by parameterValueId (DUPLICATE_CLASS_PARAMETER_ALLOWED_VALUE).
  • At most one allowed value may be the default (MULTIPLE_DEFAULT_CLASS_PARAMETER_ALLOWED_VALUES). Allowed-value state is validated before the no-change early return, so an idempotent replay still surfaces a violation instead of silently passing.
  • Deletion and allowed-value editing apply to direct rows only.

InheritanceMode — direct rows only

unique applies the assignment to this class alone; apply_to_descendants also reaches every descendant. An assignment always includes itself.

The retired wire vocabulary self_only / self_plus_descendants / descendants_only is gone from the HTTP contract — the request schemas reject those values. Persistence and Assign scripts resolve inheritance_modes.handle only (unique / apply_to_descendants). Display name is steward-facing. Inheritance.ts still maps leftover self_* tokens if one is read. descendants_only has no canonical equivalent and is rejected outright.

ChildBehavior — rendered rows only

inherit (the default) takes the ancestor’s assignment as-is, suppress removes it for this class only, and override replaces it with this class’s local values, again for this class only.

Effective parameter set

The answer to “what parameters does this class actually have” is a read model resolved on request, not a stored field. getEffectiveParameterSet resolves one class and resolveEffectiveParameterSets resolves a batch, and each decision comes back with a resolution kind and a human-readable explanation:

KindMeaning
directAssigned directly on this class
inheritedReached from an ancestor, with the path in the explanation
overrideA local direct assignment supersedes the inherited one
suppressedA local overlay removes the inherited assignment; the suppressing row’s id is reported

rendered rows are a cache for Airtable’s benefit — they let a steward see inherited parameters as rows in a grid. The resolution above is authoritative. When the two disagree, the resolver is right and the rendered rows need resyncing, which is what the rendered-template sync handler does after a parent-graph change or a new assignment.

Domain Events

Four names on one payload shape (classParameterId, objectClassId, parameterKeyId, and allowedValueCount on the replace event):

EventEmitted by
ClassParameterAssignedbind — a new direct assignment
ClassParameterAssignmentUpdatedreviseBinding, promoteToDirect, promoteToDirectAndReviseBinding — only when something actually changed
ClassParameterAllowedValuesReplacedreplaceAllowedValues, when the set differs
ClassParameterUnassignedmarkUnassigned

ClassParameterAssigned is also consumed inside the module: it triggers the rendered parameter template sync.

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, so inheritance follows one path to the root
Platform ADR-0048domain_id is the public identifier; never a rec… id in a payload
Per-file ADR-0006Canonical parameter keys and values with per-level junctions