Skip to content

Validation language: conditional rules, cross-property operands, and state-based business rules #32

Description

@woksin

Summary

The validate sub-language covers per-property declarative rules with messages, plus a validate csharp escape hatch — and nothing in between. Validating a real Arc application's document against its source shows three recurring rule shapes that fall straight through to the escape hatch (or are lost entirely when the document is generated), even though they are the most important rules an application has:

  1. Conditional rules — FluentValidation's .When(...): a rule that only applies under a condition.
  2. Cross-property comparison semanticsendDate >= startDate. The parser already accepts a bare path as a rule operand (operands are mapping-source expressions), but nothing specifies that the path resolves against a sibling property, so the meaning is undefined for consumers like Stage.
  3. State-based business rules — the Arc/DCB idiom where a command validates against injected read-model state (Must(_ => !state.IsStarted), "phase must be Contract", "candidate must exist on the work surface"). These are the rules that actually guard the domain, and they are inexpressible declaratively.

Evidence

From the Ada application (generated with Cratis.Arc.Screenplay 20.65.0):

  • 18+ whole-command rules (RuleFor(c => c).Must(...)) dropped with "'RuleFor(c => c)' does not name a property directly, so the rules declared on it were left out" — every one of them a domain business rule reading read-model state, e.g.:
RuleFor(c => c).Must(_ => !state.IsStarted)
    .WithMessage(_ => HourRegistrationMessages.StartMonth_AlreadyStarted);
RuleFor(c => c).Must(_ => summary.Phase is RequestPhase.Contract);
  • .When(...) chains dropped (no counterpart in the rule grammar).
  • 6 × SP0011 drops of the shape GreaterThanOrEqualTo(x => x.StartDate) — "carries no value to compare against" — where the natural emission endDate >= startDate parses today but has unspecified semantics.

The result is that a generated document's validate blocks contain the string-length/regex layer of validation while the domain-rule layer — the part a reader of the Screenplay actually needs to understand the behavior — is absent.

Suggested direction

  • Specify property-path operands: state in the grammar docs that a bare path operand resolves against the validated artifact's properties (endDate >= startDate), so emitters and Stage agree on the meaning. This one may be documentation-only.
  • Rule-level conditions: an optional when <condition> on a rule line or an indented condition block, reusing the existing produces when condition parser:
validate
  newEndDate > endDate when isExtension == true message "…"
  • State-based rules: a declarative form for command rules that read a read model, e.g.
validate
  require <ReadModel>.<path> <op> <value> message "…"

syntax open — the ask is that the concept exists, so generated documents (and hand-written ones) can state "this command is rejected while the month is already started" without dropping to validate csharp.

Related: closed #8 added concept-level validation; this is the command/business-rule layer on top of it. Cratis.Arc.Screenplay currently drops all of these shapes with SP0011/SP0016 diagnostics — a companion issue on Cratis/Arc covers recovering them once the language can carry them.

Context

Found while validating a generated 5,348-line document from a production Arc application (643 SP0016 warnings in one generation run; the domain-rule layer of every command is missing from the document).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions