Model summary
RootData and registries
Source file
02-data-and-registries.prl
This example introduces the data side of Primmel. It models a small roastery logbook — bean lots received at the dock, batches roasted, batches inspected — with the registries that hold those records.
The flow is intentionally linear (receive → roast → inspect). The
interesting part is the data layer, not the process layer.
Primitives introduced
| Primitive | Purpose |
|---|---|
enum |
A closed set of named values (here: roast levels). |
class#data |
A typed record that can be linked to a registry. The #data suffix is the marker. |
Helper class (no suffix) |
A reusable complex type, embeddable inside other classes. |
reference(Target#data) |
A typed foreign-key field. |
data_registry |
A named store of records of a given data class. |
output { ... } |
A process clause: which registries this process writes to. |
reference_data_registry { ... } |
A process clause: which registries this process reads from. |
Enums
enum RoastLevel {
Light { definition "Light roast" }
Medium { definition "Medium roast" }
Dark { definition "Dark roast" }
}
An enum is a closed set of named values, each with a definition. Use
enums for any field whose value comes from a fixed list.
Data classes (with #data)
class BeanLot#data {
lot_id: string { definition "Supplier lot identifier"; modality SHALL }
origin: string { definition "Country or region of origin"; modality SHALL }
received_on: datetime { definition "Date received at the roastery"; modality SHALL }
certified_organic: boolean { definition "Whether the lot is certified organic"; modality MAY }
}
Every field has:
- A name (
lot_id). - A type (
string,datetime,boolean, anenumname, areference(...), arole, or another class). - A block with at least a
definition(the human-readable meaning of the field) and amodality(SHALL/SHOULD/MAY/CAN/MUST).
The #data suffix marks this class as registerable — you can put
records of this shape into a data_registry. Without the suffix, the
class is a helper (see below).
Helper classes (without #data)
class RoastProfile {
name { definition "Profile name (e.g. City+, Full City)" }
target_level: RoastLevel { definition "Target roast level" }
development_time: string { definition "Target development time in seconds" }
notes { definition "Roaster's notes" }
}
RoastProfile is a complex type that lives inside other data
classes. It cannot be registered directly — there is no
RoastProfileRegistry. Instead, it appears as a field on
RoastBatch#data:
class RoastBatch#data {
batch_id: string { ... }
lot: reference(BeanLot#data) { ... }
profile: RoastProfile { definition "Roast profile applied" }
roast_level: RoastLevel { ... }
roast_date: datetime { ... }
moisture_pct: string { ... }
roasted_by: role { ... }
}
This distinction matters: only class#data declarations appear in
registries. Helper classes are how you build composite field types.
References
lot: reference(BeanLot#data) {
definition "Bean lot used for this batch"
modality SHALL
}
A reference(Target#data) field is a typed foreign key. It points at
another record in another registry. At evaluation time, tooling follows
these references to traverse the data graph — e.g. from a
RoastBatch to the BeanLot it was roasted from.
The role type (used by roasted_by) is a special reference to a
person in a role, rather than to another data record.
Data registries
data_registry BeanLotRegistry {
title "Bean lots"
data_class BeanLot#data
}
data_registry RoastBatchRegistry {
title "Roast batch logbook"
data_class RoastBatch#data
}
A data_registry is a named store of records. It declares which
class#data shape its records conform to. registries are the things
that processes read from (reference_data_registry) and write to
(output).
Processes that read and write data
process ReceiveBeans {
name "Receive a bean lot from an approved supplier"
actor Roaster
output {
BeanLotRegistry
}
}
process RoastBatch {
name "Roast a batch of beans"
actor Roaster
reference_data_registry {
BeanLotRegistry
}
output {
RoastBatchRegistry
}
}
process InspectBatch {
name "Inspect a roasted batch"
actor QA
reference_data_registry {
RoastBatchRegistry
}
}
| Process | Reads | Writes |
|---|---|---|
ReceiveBeans |
— | BeanLotRegistry |
RoastBatch |
BeanLotRegistry |
RoastBatchRegistry |
InspectBatch |
RoastBatchRegistry |
— |
The data flows down the chain. Each registry becomes the join point between the process that produces its records and the process that consumes them.
The diagram
canvas Root {
elements {
Start1 { x 0 y 0 }
ReceiveBeans { x -100 y 100 }
RoastBatch { x -100 y 200 }
InspectBatch { x -100 y 300 }
}
process_flow {
Edge1 { from Start1 to ReceiveBeans }
Edge2 { from ReceiveBeans to RoastBatch }
Edge3 { from RoastBatch to InspectBatch }
}
data {
BeanLotRegistry { x 150 y 130 }
RoastBatchRegistry { x 150 y 230 }
}
}
The data { ... } block places the registries on the diagram at the
given coordinates. Visually, the registries sit to the right of the
process flow, indicating that they are external to the flow itself but
referenced by it.
What this example leaves out
- Compliance — no
provision, nomodalityon processes. Added in compliance and measurement. - Branching — the flow is a straight line. Branching is added in process flow.
- Subject bindings — provisions that evaluate against registry records. Added in compliance and measurement.
Model elements
▾Roles2
RoasterQAQuality assurance
▾Processes3
ReceiveBeansReceive a bean lot from an approved supplieractor: RoasterRoastBatchRoast a batch of beansactor: RoasterInspectBatchInspect a roasted batchactor: QA
▾Events1
Start1start
▾Data Classes3
BeanLot#dataRoastProfileRoastBatch#data
▾Registries2
BeanLotRegistryRoastBatchRegistry
▾Enums1
RoastLevel