PDD version 0.34.1

Business Rules

A Business Rule is something that must always hold in your product, or who may do what. It comes in three kinds:

  • a constraint: a refund never exceeds the charge;
  • a derivation: Total charged equals subtotal plus tax minus discount;
  • a permission: an order's margin is for operators only.

Permissions live only here. A Scenario shows someone doing a thing; the Rule says whether they may.

In practice

In your productThe RuleKind
A refund can never repay more than the order costA refund never exceeds the chargeConstraint
An order is confirmed only after its payment succeedsPayment before confirmationConstraint
The total is always subtotal plus tax minus discountTotal chargedDerivation
Only operators see what the store earns on an orderMargin is for operatorsPermission
A shopper edits delivery details only while the order is unpaidDelivery details are editable while unpaidPermission
Once refunded, an order is never cancelled, by anyoneA refunded order is never cancelledPermission

When you create one

  • It holds across behaviors, a place, or a thing. Something true of exactly one Capability is that Capability's own condition Step or Outcome, not a Rule.
  • One Rule per assertion, however many places it applies. Payment before confirmation governs every way an Order becomes Confirmed; it is not copied into each Capability that confirms one.
  • Write what must remain true, not a sequence. A refund never exceeds the charge, not check the amount, then issue the refund.
  • Alternative grants share one Rule. Two permission Rules on the same operation must both allow it, so the owner may and an admin may, written apart, would mean only someone who is both.
  • One permission Rule per operation. Creating, changing and removing a Card are three Rules, each naming its effect, even when the same people may do all three.
  • What goes with another is the other's to permit. A Rule on removing comments never selects the comments a deleted card takes with it, and a Rule on adding members never selects the first membership a new board is created with.
  • A condition reads one instance, never any of many. The actor itself; a per-board Role on the related path, the acting person's own membership; the one the governed thing leads to, such as the Quiz a Question is in; or Store settings marked singleton.
  • Ordinary copy is design; contractual wording is a Rule. When exact words are required, the Rule says so and cites the authoritative Reference.

The file

business-rules/<id>.md. The id and H1 state the assertion, never a verb or a mechanism; the lead paragraph is the Rule itself.

business-rules/a-refund-never-exceeds-the-charge.md
---
appliesTo:
  - type: entity
    id: refund
    facts: [Amount]
---

# A refund never exceeds the charge

A refund's Amount is at most the Total charged of the order it repays.

## Rationale

The order is the audit boundary for the refund.
Field or sectionRequiredSays
appliesToyesWhat the Rule governs: at least one target, below
permitsnoWho may perform the selected operation; see Permission
referencesnoCode or documents behind it, in the Reference shape
H1 and leadyesThe assertion, stated plainly
## IntentnoThe outcome the Rule protects
## RationalenoWhy it is necessary now, not which designs were rejected

What a Rule applies to

The Rule owns its targets. Nothing else lists the Rule; every backlink is derived.

Behavioral and Context targets

typeApplies to
capability, journeyThat behavior, wherever it is supported
capability-scenario, journey-scenarioOne Scenario of it
contextA place, independent of any behavior

A behavioral target may narrow itself with contexts to the places it holds in. A place target looks like this:

appliesTo:
  - type: context
    context:
      place: operator-cli

Do not target a Capability and one of its own Scenarios; the Capability already covers it.

Entity targets: an operation on a thing

An Entity target names a thing and, optionally, the operation on it, such as changing an Order's delivery details, refunding an Order, reading its Margin:

appliesTo:
  - type: entity
    id: order
    effect: changes          # optional: creates | changes | removes | reads
    from: Confirmed          # optional: the State it leaves
    to: Refunded             # optional: the State it lands in
    facts: [Margin]          # optional: only operations on these facts
    contexts: [{ place: admin-web::order-console::order-detail }]   # optional

The target selects which operations; whether the thing is in some State when it happens is a condition on a grant. Write the smallest selector that is true: refunds only ever leave Confirmed, so { effect: changes, to: Refunded } is the Rule, and it still covers a refund added later from another State.

Permission

permits says who may perform the selected operation, and it needs Entity targets only. A read restricted in one place (a download page) is effect: reads on the Entity narrowed with contexts to that place. A restricted read on the same Screen as ordinary reading cannot be told apart yet: say so in the Rule's prose.

permitsSays
omittedThis Rule makes no permission claim
[]Nobody may; a Step doing it is a lint error
a list of grantsAnyone matching one grant may
business-rules/delivery-details-are-editable-while-unpaid.md
---
appliesTo:
  - type: entity
    id: order
    effect: changes
    facts: [Delivery details]
permits:
  - related: [{ verb: owns, entity: shopper }]
    when: [{ state: Pending }]
---

# Delivery details are editable while unpaid

A shopper edits an order's delivery details only while the order is still
unpaid.

Grants in one Rule are alternatives; the keys inside one grant must all hold. When several permission Rules select the same operation, each must allow it. An operation no permission Rule selects is open.

Grant keys

Every grant names a who; when only narrows it.

KeySaysExample
actorsThese Entities mayactors: [store-admin]
relatedWhoever stands in this relation to the thing mayrelated: [{ verb: owns, entity: shopper }], the Order's own Shopper
selfThe thing itself mayself: true, a Shopper edits their own address
unattendedThe Product's own schedule mayunattended: true, cancel when the payment window closes
configuredByWhoever the customer configures mayconfiguredBy: store-settings
whenOnly while these conditions all holdwhen: [{ state: Pending }]

A when condition is a State of the thing, or a fact with one operator (over, under, at-least, at-most, is, is-not, present, absent):

when:
  - { state: Pending }                                                    # the thing's State
  - { fact: Total charged, at-most: 100 }                                 # a fact of the thing
  - { entity: store-settings, fact: Self-service cancellation, is: true } # a setting
  - { fact: Total charged, over: { entity: store-settings, fact: Refund approval threshold } } # a threshold the store sets

A setting that only decides who may do something is a condition, not a Variation. Self-service cancellation on or off is one grant's when. Business Rules vary only when a setting switches between two complete policies, such as Standard or Strict refund review.

A product's own roles

Each role the Product ships is an Entity that acts, granted through actors: one Entity per role, never one per role a customer configures. A role held per team or workspace goes through a membership Entity that names it, reached by related.

How it connects

  • A Rule reaches every Capability whose Steps perform the operation it selects, and governs that move in the Entity's Lifecycle.
  • It cites Entity facts and States by exact name, and related walks the Entity's declared relations.
  • Steps and Screens are checked against its grants: a Step's actor, or a Screen's audience, must be someone a grant could admit.
  • Domains are never targets; a Rule's Domains are derived from what it governs.

What lint checks

Errors:

  • A missing H1 or lead, or an empty appliesTo.
  • A target whose id, State, fact or place does not resolve; from on a creates or reads target, to on a removes or reads one; a Capability or Journey targeted beside one of its own Scenarios.
  • permits on a Rule with a non-Entity target.
  • A grant that names nobody; actors, a related end or self naming an Entity that does not act; a related hop that matches no relation, or more than one, or walks a self-relation (a Comment replying to a Comment has no direction); a grant whose actors leaves out the Entity its related path ends on, so nobody could satisfy it.
  • A condition without exactly one of the eight operators, a fact or State that does not resolve, or a state on a creates target; a condition entity or threshold that could be any of many instances.
  • A Step performing an operation permits: [] forbids; a governed operation whose Step has no actor, or one no grant could admit; an unattended Scenario no unattended grant allows; a Screen presenting governed facts to an audience no grant admits.

Warnings:

  • A Rule whose only target is one behavior with no contexts: it belongs to that Capability.
  • Two permission Rules with identical targets.
  • A from or state condition every selected Step already satisfies.
  • An id that opens with a verb acting on something the model declares, such as cancel-unpaid-orders beside an Order Entity.
  • A grant that names ai-agent without a related path to the person whose agent it is.
  • An Actor changing or removing a thing whose other changes Rules govern, when no permission Rule selects that change.

A permission Rule that is an alternative in a Variation skips the Step and Screen checks; pdd-verify checks it against the code.