Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions rsworkspace/crates/trogon-atlas-server/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn entity_kind_catalog() -> Vec<pb::EntityKindInfo> {
k(Command, "ENTITY_KIND_COMMAND", "command", "Command", "Commands",
"An imperative intent issued by a persona or processor. Wrapped by a CommandSlice that lists the events it emits."),
k(ReadModel, "ENTITY_KIND_READ_MODEL", "readModel", "Read Model", "Read Models",
"A projection of one or more events shaped for a single consumer (UI, automation, search). Decision #34: one read model per consumer."),
"A projection of one or more events shaped for a single consumer (UI, automation, search). Give each consumer its own read model; declare a SharedConsumersAnnotation when one read model serves several on purpose."),
k(Processor, "ENTITY_KIND_PROCESSOR", "processor", "Processor", "Processors",
"An automated actor. Reads a read model, issues exactly one command. Each branching outcome is its own processor."),
k(Ui, "ENTITY_KIND_UI", "ui", "UI", "UIs",
Expand Down Expand Up @@ -134,13 +134,13 @@ pub fn entity_kind_catalog() -> Vec<pb::EntityKindInfo> {
k(Tracker, "ENTITY_KIND_TRACKER", "tracker", "Tracker", "Trackers",
"An open question, decision, or follow-up attached to a subject. Doesn't render on the board."),
k(BoundedContext, "ENTITY_KIND_BOUNDED_CONTEXT", "boundedContext", "Bounded Context", "Bounded Contexts",
"A context boundary: owns its events/commands/RMs. Decision #27: slug == namespace."),
"A context boundary: owns its events/commands/RMs. Its slug must equal its namespace, so each namespace has exactly one bounded context."),
k(Domain, "ENTITY_KIND_DOMAIN", "domain", "Domain", "Domains",
"The container word for the business / problem space. Usually exactly one per project."),
k(Subdomain, "ENTITY_KIND_SUBDOMAIN", "subdomain", "Subdomain", "Subdomains",
"One problem the business must be good at. Classified CORE / SUPPORTING / GENERIC."),
k(Schema, "ENTITY_KIND_SCHEMA", "schema", "Schema", "Schemas",
"A named field shape that field-bearing entities (events/commands/RMs) reference. Decision #33."),
"A named field shape that field-bearing entities (events/commands/RMs) reference. Two entities that hold the same schema are the same type, because shape is declared, not inferred."),
k(Project, "ENTITY_KIND_PROJECT", "project", "Project", "Projects",
"The codebase / repository / customer the model was mapped from. The outermost grouping in a multi-project store."),
k(Screen, "ENTITY_KIND_SCREEN", "screen", "Screen", "Screens",
Expand Down Expand Up @@ -280,7 +280,7 @@ pub fn rule_catalog() -> Vec<pb::ValidationRule> {
"Every field a command carries is data the runtime accepts. If that field does not appear on any of the slice's emitted_events AND does not appear in the command's swimlane.state, the model is silent about what happened to the value: the command took it in, the events do not record it, and the aggregate does not claim it as state. Either add the field to one of the emitted events, declare it on the lane's swimlane.state, or drop it from the command.",
CommandSlice, "command", "model"),
r("SLICE_STALE_REF", Warning, "Slice ref pins an older version than the latest loaded",
"A slice points at a specific (namespace, slug, version) of a command, ui, persona, processor, event, or read model, but a newer version of that entity exists in the loaded model. Mid-flight migration signal (Decision #24): the slice is still wired to the previous incarnation. Bump the ref to the latest version, or attach a PinnedRefAnnotation with a non-empty doc to the slice to justify staying pinned.",
"A slice points at a specific (namespace, slug, version) of a command, ui, persona, processor, event, or read model, but a newer version of that entity exists in the loaded model. Mid-flight migration signal: the slice is still wired to the previous incarnation. Bump the ref to the latest version, or attach a PinnedRefAnnotation with a non-empty doc to the slice to justify staying pinned.",
Unspecified, "", "model"),
r("UI_USED_AS_INPUT_AND_OUTPUT", Warning, "UI used as both trigger and render target",
"A UI appears as both the trigger for a CommandSlice AND the render target for a UiSlice. One surface in both roles is usually a modeling mistake: split the form from the display, or attach a MixedSurfaceAnnotation explaining why.",
Expand Down Expand Up @@ -363,7 +363,7 @@ pub fn rule_catalog() -> Vec<pb::ValidationRule> {
ReadModelSlice, "source_events", "storyboard"),
// ----- strategic (Domain / Subdomain / BoundedContext) -------------------
r("CONTEXT_SLUG_MISMATCH", Error, "Bounded context slug differs from namespace",
"Decision #27: a BoundedContext's slug must equal its namespace; this guarantees context ids are globally unique by namespace alone.",
"A BoundedContext's slug must equal its namespace; this guarantees context ids are globally unique by namespace alone.",
BoundedContext, "id", "strategic"),
r("DOMAIN_SLUG_MISMATCH", Error, "Domain slug differs from namespace",
"A Domain's slug must equal its namespace: the namespace IS the domain identity.",
Expand Down Expand Up @@ -414,7 +414,7 @@ pub fn rule_catalog() -> Vec<pb::ValidationRule> {
"A relationship is declared but no subscription read model between the two contexts backs it. Declare the seam (an RM in the downstream context whose source_events live in the upstream).",
BoundedContext, "relationships", "strategic"),
r("SEAM_WITHOUT_RELATIONSHIP", Warning, "Structural seam present without relationship intent",
"A downstream context subscribes to an upstream event but no ContextRelationship records the strategic intent. Declare one (Decision #30).",
"A downstream context subscribes to an upstream event but no ContextRelationship records the strategic intent. Declare one on the downstream context so the relationship intent and the seam data agree.",
BoundedContext, "seams", "strategic"),
// ----- project (multi-project hygiene) -----------------------------------
r("PROJECT_SLUG_MISMATCH", Error, "Project slug differs from namespace",
Expand Down
24 changes: 24 additions & 0 deletions rsworkspace/crates/trogon-atlas-server/tests/catalog_docs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Agents read these docs through list_entity_kinds and list_validation_rules
// and have no way to look up a numbered decision, so each doc must state
// its rule in full.

use trogon_atlas_server::validation::{entity_kind_catalog, rule_catalog};

#[test]
fn catalog_docs_do_not_cite_decision_numbers() {
let mut offenders: Vec<String> = entity_kind_catalog()
.into_iter()
.filter(|k| k.doc.contains("Decision #"))
.map(|k| k.proto_name)
.collect();
offenders.extend(
rule_catalog()
.into_iter()
.filter(|r| r.doc.contains("Decision #"))
.map(|r| r.code),
);
assert!(
offenders.is_empty(),
"catalog docs cite a decision number instead of stating the rule: {offenders:?}"
);
}
Loading