Skip to content

intent: two values of one row, compared - checks: compare (#7095) - #7121

Merged
delchev merged 1 commit into
masterfrom
issue-7095-check-compare
Sep 8, 2026
Merged

intent: two values of one row, compared - checks: compare (#7095)#7121
delchev merged 1 commit into
masterfrom
issue-7095-check-compare

Conversation

@delchev

@delchev delchev commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

checks: knew exactlyOne, itemsSumEqual and itemsMin. No kind related two values of the SAME record, so the most ordinary rule a business document has could not be declared at all: an invoice was saved (200), issued, and overdue the moment it existed, because its date moved three months forward and its due date stayed where it was. The only expressible workaround was a hand-written calculated action per document type, which corrects the value instead of refusing it - so the clerk is never told their date was overruled, and every document type needs its own class for one comparison.

checks:
  - { kind: compare, field: due,  op: ge, than: date,  message: "Due cannot be before the invoice date" }
  - { kind: compare, field: paid, op: le, than: total, message: "Paid cannot exceed the total" }

Row-level like exactlyOne: enforced in every generated controller's validate() - the entity, personal and partner surfaces - as a 400 carrying the authored message, and therefore taking no status gate: a rule about two values of one row holds from the first save, not from a transition. op: (ge/gt/le/lt/eq/ne) is spelled out because an omitted operator has no defensible default - "not before" and "strictly after" are different rules and the wrong guess is silent.

Both operands are the entity's own fields - a comparison of two foreign keys means nothing - and must sit in ONE comparison family, which is what the generated code needs: two temporals compare through their own compareTo (a LocalDate does not compare to an Instant), two numbers by value through BigDecimal so a decimal against a long stays exact. Only dates, timestamps and numbers compare; a string, a boolean or a month/week label is refused rather than silently ordered lexicographically, as is a field compared with itself. An absent operand is not a violation - a comparison is about two values that exist, and requiredness is its own declaration.

The generated app test learns the rule with it: its sample values are per-type constants, so two dates come out EQUAL and a strict comparison (gt/lt/ne) would have every generated suite refused with 400 by the check the module just declared. The .test manifest now carries the comparison and the runner derives the left operand from the right by the operator's own smallest step.

Verification

  • IntentParserTest.compareChecksParseAndValidate - parse plus every refusal (unknown op, mixed families, a non-comparable type, a dangling operand, a self-comparison, a status gate, missing operands).
  • EdmIntentGeneratorTest.compareChecksEmitOperatorAndFamily - the two PascalCased properties, the Java operator and the family flag reach the .model.
  • AppTestIntentGeneratorTest - the comparison reaches the .test manifest.
  • IntentEmissionCoverageIT (green, the only local check that COMPILES the generated client Java): both emitted branches are asserted in EntryController, and at runtime a due date behind the entry date is refused with the authored message (400), the same date is accepted, and a record carrying no due date passes.
  • mvn formatter:validate and the -P release javadoc pass on the touched modules.

Docs: dirigible-io/dirigible-io.github.io#234 (merged), IntentFile/intent-specification#68, IntentFile/intentfile.github.io#51.

Fixes #7095

`checks:` knew `exactlyOne`, `itemsSumEqual` and `itemsMin`. No kind related two
values of the SAME record, so the most ordinary rule a business document has could
not be declared at all: an invoice was saved (200), issued, and overdue the moment
it existed, because its date moved three months forward and its due date stayed
where it was. The only expressible workaround was a hand-written calculated action
per document type, which CORRECTS the value instead of refusing it - so the clerk
is never told their date was overruled.

    checks:
      - { kind: compare, field: due,  op: ge, than: date,  message: "Due cannot be before the invoice date" }
      - { kind: compare, field: paid, op: le, than: total, message: "Paid cannot exceed the total" }

Row-level like `exactlyOne`: enforced in every generated controller's `validate()`
(the entity, personal and partner surfaces) as a 400 carrying the authored message,
and therefore taking no `status` gate - a rule about two values of one row holds
from the first save, not from a transition. `op:` is spelled out because an omitted
operator has no defensible default ("not before" and "strictly after" are different
rules and the wrong guess is silent).

Both operands are the entity's own FIELDS - a comparison of two foreign keys means
nothing - and must sit in ONE comparison family, which is what the generated code
needs: two temporals compare through their own `compareTo` (a `LocalDate` does not
compare to an `Instant`), two numbers by value through `BigDecimal` so a `decimal`
against a `long` stays exact. Only dates, timestamps and numbers compare; a string,
a boolean or a `month`/`week` label is refused rather than silently ordered
lexicographically, as is a field compared with itself. An ABSENT operand is not a
violation - a comparison is about two values that exist, and requiredness is its
own declaration.

The generated app test learns the rule with it: its sample values are per-type
constants, so two dates come out EQUAL and a strict comparison (`gt`/`lt`/`ne`)
would have every generated suite refused with 400 by the check the module just
declared. The `.test` manifest carries the comparison and the runner derives the
left operand from the right by the operator's own smallest step.

Fixes #7095
@delchev
delchev force-pushed the issue-7095-check-compare branch from bd40dd6 to faf1299 Compare September 8, 2026 06:31
@delchev
delchev merged commit fdf4dd0 into master Sep 8, 2026
10 checks passed
@delchev
delchev deleted the issue-7095-check-compare branch September 8, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

intent: checks: has no field-comparison kind - 'Due >= Date' cannot be declared, so an invoice is saved and issued already overdue

1 participant