Context
In BSFG, a fact is represented as:
fact = (subject, predicate, object_json)
The subject identifies what the fact is about. The object carries structured payload. The predicate therefore carries the relationship that the fact asserts.
Predicate design affects:
- readability of facts in logs and diagnostics
- stability of cross-system contracts
- replay and projection logic
- how much accidental business detail leaks into identifiers
The architecture needs a predicate style that is stable, compact, and expressive without turning predicates into sentence fragments or domain-specific mini-protocols.
Options Considered
| Option | Description | Benefits | Drawbacks |
|---|---|---|---|
| Free-form text predicates | Allow arbitrary natural-language predicate phrases. |
- maximal local flexibility
- easy to invent ad hoc
|
- poor consistency
- hard to govern
- weak replay/projection discipline
|
| Noun-only property names | Use property-style names such as status, owner, or attachment. |
- compact
- familiar in some data models
|
- ambiguous for process facts
- weaker relation semantics
- less clear in eventful manufacturing contexts
| | Predicates embedded into subject | Encode relation meaning inside the subject namespace rather than as a separate predicate field. |
- fewer top-level fields
- can feel convenient in the short term
|
- subject identity becomes unstable
- routing and semantics get mixed
- harder partitioning and governance
| | Stable lower_snake_case relation names (Selected) | Use compact relation names as predicates, organized by meaning rather than by transport mechanics. |
- clear fact readability
- stable contracts across producers and consumers
- good fit for replay and projection logic
- keeps subject identity separate from relation meaning
|
- requires vocabulary governance
- teams must resist inventing overlapping synonyms
|
Decision
BSFG predicates use stable lower_snake_case relation names.
The predicate expresses the relation asserted about the subject, while detailed parameters remain inside object_json.
subject = batch:PlantA/B1042
predicate = step_completed
object = { "step": "sterile_filter", "result": "ok" }
Predicates should be concise relation names, not sentence fragments and not encoded summaries of the whole payload.
Predicate families include:
- domain predicates:
step_started,step_completed,alarm_raised,sample_taken - state predicates:
state_is,status_is,mode_is,value_is - protocol predicates:
received_by,confirmed_by,stored_at,attached_as
BSFG does not enforce a closed global predicate list, but predicate names must remain stable within a bounded context and should be curated to avoid synonym drift.
Consequences
Benefits:
- facts remain readable in logs, dashboards, and replay tools
- subject identity stays clean and stable
- object payload can evolve without renaming the relation
- projection code can organize logic around stable relation names
Tradeoffs:
- predicate naming requires shared discipline
- governance is needed to avoid overlapping forms such as
completedvsstep_completed - some teams may initially prefer more free-form event naming and will need to adapt