| name | python-verbs-success |
| description | Build the two behavior constructs — verb (state transition) and derivation (pure computed fact) — the only legal methods on a model. Fires before writing any method on the consistency model that isn't a derivation, any method/property/computed field on a frozen model, a standalone function computing from a model's fields, a stored derivable field, a fetch-and-return method, or a multi-step construction chain. |
Verbs
The behavior constructs: what a model does, as opposed to what it is (python-values-success). A verb mutates the one live consistency model; a derivation computes a fact from a frozen value's own fields, forever the same answer for the same fields.
Verb
Definition
A state-transition method on the consistency model. Its parameter is the innermost constructed value it consumes; its body contains at most one construction statement, with constituents constructing inside that call; it may capture a foreign reply before the construction, re-point a state field to the constructed fact, and emit the constructed fact through a client field.
Required Form
def book(self, report: VenueFill) -> None:
self.latest = Position(prior=self.latest, fill=report)
self.bus.publish(self.latest)
self.ledger.append(self.latest)
Position.fill is typed Fill, so the Fill constructs from the VenueFill inside the Position construction call. The state field is the constructed value's only name, and every emit passes the proven value itself; serialization happens at the client binding or the route reply, never here.
A yielding verb yields constructed facts:
async def watch(self, account: AccountId) -> AsyncIterator[Fill]:
async for report in self.feed.subscribe(account):
yield Fill.model_validate(report)
A capturing verb feeds the ordered union's constructor:
async def reconcile(self, account: AccountId) -> None:
try:
raw: object = await self.ledger.position(account)
except PositionNotFoundError as signal:
raw = signal
self.recorded = LedgerReplyConstructor.validate_python(raw)
Sorting Rules
A fact implied by already-proven fields is a derivation, not a verb. A method that only retrieves and returns is not a verb; consumers read facts that transitions establish. Construction of the request shape belongs to the route; the verb receives the innermost value, never a transport wrapper.
Replaced Forms
A multi-step body staging constructions in sequence is the work stolen from a constructor: a constituent constructed in a separate statement before the composite that holds it restates what the composite's call proves. A fetch-and-return method is a repository surface given a verb's name. Serialization in the body is the transport's concern restated in the domain.
Construct-Specific Doctrine
The body contains at most one construction statement; constituent values construct inside that one call. It may also: capture a foreign reply before the construction (one call assigned, each declared exception assigned to the same variable), assign the constructed fact to a state field, and emit the constructed fact through a client field. An effect is never emitted before the fact is constructed. A verb that constructs nothing, emits nothing, and yields nothing declares no transition.
Allowed Patterns
-> None transition: at most one construction, a state-field assignment, emits of the proven fact
- the capture before the construction, exactly the three-line form, feeding the ordered union's constructor
- a yielding verb constructing and yielding one fact per arrival
- a returning verb whose return is read from the fact its body constructs
Forbidden
- more than one construction statement in a body
- a constituent constructed in a separate statement before its composite
model_dump, model_dump_json, or .root in the body
- a parameter holding a transport wrapper
- a method that only retrieves and returns
match, if/elif, or isinstance in the body
- a hand-assembled dict where a constructed type belongs
- a stub body:
raise NotImplementedError, bare ..., or pass
Halt Rule
Halt when the transition needs a second construction statement, a computation no derivation carries, a branch, or a parameter only available as a transport wrapper. Report the row and the statement that will not fit: the composite, the derivation, or the route's unwrap is missing from the table, and the table is not finished.
Derivation
Definition
A fact that is a pure function of a frozen value's already-proven fields, written on the model that owns them. It takes only self, its body is one returned expression, and it returns a declared type, model, or union. The same fields yield the same fact every time, because the value they compose never changes.
Required Form
class Exposure(RootModel[Decimal], frozen=True):
root: Decimal = Field(ge=0)
class Fill(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)
order_id: OrderId
account_id: AccountId
fill_price: Price
filled_quantity: Quantity
@cached_property
def exposure(self) -> Exposure:
return Exposure(self.fill_price.root * self.filled_quantity.root)
Because a derivation is a pure function of frozen fields, @property and @cached_property return the same fact and differ only in substrate: @property recomputes it on each read, @cached_property computes it once and remembers. The choice is cost, never meaning: @property for a cheap fact (a constant, a single lookup), @cached_property when the fact is expensive, recursive, or read repeatedly, where the cache is what lets a recursion settle in one pass and a materialization happen once. @computed_field above @cached_property is the only one of the three that changes meaning: it writes the derived fact into every serialization, so it is used exactly when the fact is part of a contract shape.
Construction recurses, so a derivation over a recursive model reaches arbitrary depth with no loop: the comprehension inside the one returned expression is the whole traversal.
class Portfolio(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
exposure: Exposure
children: tuple["Portfolio", ...] = ()
@cached_property
def total_exposure(self) -> Exposure:
return Exposure(self.exposure.root + sum(c.total_exposure.root for c in self.children))
The contract-shape form:
class Quote(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
bid: Price
ask: Price
@computed_field
@cached_property
def mid(self) -> Price:
return Price((self.bid.root + self.ask.root) / 2)
Sorting Rules
A fact that differs by which union variant holds is each variant's own same-named derivation, read from the union value. A question with an input is a query model, not a parameterized method. A state transition belongs to a verb on the consistency model; a derivation stores nothing and changes nothing. The line between a derivation and a field is structural: if a pure function of the model's own fields yields the value, it is a derivation and is never also stored, since a stored copy of a derivable fact is one meaning in two structures. A value that no function of the fields can yield, because it depends on the clock, a foreign read, a random source, or input the model does not keep, is not a derivation at all; it is a field, computed once where the value is born and passed into construction. On Quote, mid is a derivation, a function of bid and ask; the moment the quote was observed would be a field, because no function of bid and ask yields it.
Replaced Forms
A fact you are about to compute in a function, a step, or a loop is a derivation that has not found its model yet: name it on the value whose fields imply it, and the procedure is gone, the work done by the construction the derivation returns. A standalone function computing from a model's fields is that derivation escaped from its owner. A stored derivable field is a second copy of a fact kept in agreement by hand. A bool-returning check is an undeclared union: a staleness check returns Fresh | Stale, each variant carrying its own facts.
Construct-Specific Doctrine
The computation. A derivation is a function from the model's proven fields to the fact it returns, which is a proof that those fields imply that fact. The computation is the proof term, and it is determined meaning, not the builder's to invent: that exposure is price times quantity and not over it is a domain decision, made once where the model lives, never at build time. The operations that compose the proof are a closed algebra, the way the constructs are a closed set: arithmetic over the fields, a fold over a collection (the catamorphism that carries the one recursion), the extremum of a collection along a ranked closed value space (the same catamorphism choosing rather than summing), selection of an element by its key, and a lookup of a closed value through a total case table. A computation that needs an operation the algebra does not hold is a reported gap, never free code. The only part of a derivation the builder decides is cost, not meaning: whether the fact is recomputed on each read or memoized once.
Query model. A question with an input is a composite fact: a frozen BaseModel holding the input and the value being queried, with the answer as a derivation on it, returning a constructed choice.
class PriceQuery(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
book: PriceBook
product: ProductId
@cached_property
def answer(self) -> PriceFound | PriceMissing:
return next(
(PriceFound(price=price) for key, price in self.book.root.items() if key == self.product),
PriceMissing(product=self.product),
)
Allowed Patterns
- a derivation taking only
self, body one returned expression, returning a declared type, model, or union
@property to recompute a cheap fact, @cached_property to compute a costly, recursive, or repeatedly-read fact once, both pure over the fields
@computed_field above @cached_property when the fact belongs to a contract's serialized shape
- a comprehension or generator expression inside the one returned expression
- the same-named derivation on every variant of a union, read from the union value
- a frozen query model holding the input and the queried value, its derivation returning the answer
Forbidden
- a free function computing from a model's fields; a helper or utils entry
- a parameterized method on a frozen value
- a derivable field stored on a model
- a body that reads anything but the model's own fields (the clock, a client, a random source); a
@cached_property freezes its first-read value and a @property returns a different value each call, so neither is a fact of the fields
- a derivation returning bare
bool, str, or int
- a ternary,
and, or, match, or private helper call inside the body
- serialization inside a derivation
Halt Rule
Halt when the fact will not reduce to one returned expression over the model's own fields, when the computation needs an operation the compute algebra does not hold, or when it needs an input no query model row declares. Report the row and the fact: the model is missing a field, a constituent model, or a query model, or the algebra is missing an operation, and the table is not finished.