feat(intent): a register lookup can filter the register with a constant predicate - #6858
Merged
Merged
Conversation
A `resolves:` lookup could not narrow its register by a literal. Every `match:`
pair binds a register column to a column of the RECORD, so "and only the rows
that are still valid" had no form at all - and a register is exactly the kind of
table that keeps its corrections. The cancelled row stays beside the active one
over the same dates, so both cover the violation date, and a lookup with one
right answer reports `ambiguous`, routes to manual handling and logs "multiple
matches". Every correction ever made earns that, quietly, and it gets worse the
longer the register lives.
Add an optional `where: { <register property>: <literal> }`, ANDed onto the same
Criteria as the match keys so it narrows the query rather than being applied
after the period comparison.
Three things are deliberately not a mirror of the relation-level `where:`.
Multiple pairs are allowed: that one is capped at a single pair because it lands
in two EDM attributes, whereas these are chained `eq` calls where a second
condition costs nothing. A pair naming the register's `function: EntityStatus`
relation may use the seeded NAME, resolved on the REGISTER's own nomenclature -
the record's would hand back a plausible id from the wrong lifecycle - and only
that pair is offered to the resolver, so an ordinary `kind: PRIMARY` is not
reported as an unknown status. And a pair repeating a `match` key is refused
rather than ANDed: on a column already bound to the record a literal either
repeats the match or contradicts it into matching nothing, and which one depends
on data the parser cannot see.
The generated handler names the filter in its javadoc and in the notFound log, so
a filter that is too narrow reads as a filter rather than as missing data.
Also two defects in the javaLiteral helper this reuses: a Boolean was quoted into
the string "true", which matches no boolean column, and a backslash was not
escaped.
Two separate races against one cold page load, both of which report a healthy Builder as broken. openBuilder() returns as soon as navigation does and the shell's scripts are deferred, so the probe for Alpine and the Builder's stores sampled a page that had not finished booting. That is the failure seen on this PR's smoke run and on eclipse-dirigible#6841 - a test with nothing to do with either change. It now polls the same condition instead; a mis-ordered or missing script still never registers the stores, so a real breakage still fails, just at the deadline rather than instantly. The second race had not been drawn yet. This class configures no Selenide timeout, so every shouldBe without an explicit Duration gets the library default of four seconds - and three of them wait on elements Alpine renders after those stores register, one straight after a page load. Four seconds there is a race against the boot, not a check on it. They get explicit deadlines, in line with every other wait in the class. Left alone: the waits that follow an already-satisfied wait on the same rendered region. The DOM is present by then, and inflating every timeout only makes a genuine breakage take minutes to surface instead of seconds.
# Conflicts: # components/engine/engine-intent/CLAUDE.md # components/engine/engine-intent/src/main/resources/intent-assistant-guide.md # tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/ui/tests/IntentBuilderShellIT.java
…dering (eclipse-dirigible#6813) GlueGenerator.copy REMOVES an absent key, and Velocity renders the removed reference as its own literal - so a resolves entry written before the where: keys existed emitted ${filterSummary} into the generated javadoc and read as filtered while filtering nothing. The binder now defaults filters to the empty list and filterSummary to "" - the no-filter shape, the same migration pattern the create-from's topicSuffix uses. Caught by ModelGenerationIT's unresolved-reference guard on the pre-existing fixture entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds an optional
where: { <register property>: <literal> }toresolves:, ANDed onto the sameCriteriaas the match keys.A
resolves:lookup had no way to narrow its register by a constant. Everymatch:pair binds a register column to a column of the record, so "and only the rows that are still valid" had no form at all — and a register is exactly the kind of table that keeps its corrections:A fine on 2026-03-14 should resolve to Ivanov. Both rows match and both cover the date, so the lookup reported
ambiguous, routed to manual handling and logged "multiple matches" — for a register with exactly one valid answer. Every correction ever made earned that, quietly, and it got worse the longer the register lived.Three things are deliberately not a mirror of the relation-level
where:widgetOptionsFilterBy/widgetOptionsFilterValue); these become chainedCriteria.eqcalls, where a second condition costs nothing.StatusSymbolResolver.rewriteResolvesresolves against the record's lifecycle, which for a register filter hands back a plausible id from the wrong entity. NewrewriteResolveWhereresolves againstfrom:. The tests seedACTIVEas2on the record and7on the register precisely so that mistake cannot pass.kind: PRIMARYto it would report a perfectly valid string as an unknown status.Refused rather than guessed: a
wherepair that repeats amatchkey. On a column already bound to the record a literal either says the same thing twice or contradicts it into matching nothing, and which one it is depends on data the parser cannot see.The generated handler names the filter in its javadoc and in its
notFoundlog line, so a filter that is too narrow reads as a filter rather than as missing data.Also fixes two latent defects in the
javaLiteralhelper this reuses: aBooleanwas quoted into the string"true"(which matches no boolean column), and a backslash was not escaped.Verification
engine-intent762/762;IntentEngineIT55/55.GlueResolveWhereTestcases fail without the change — includingunknown key [where], confirming the closed vocabulary really did reject it.What issues does this PR fix or reference?
Release Notes
An effective-dated register lookup (
resolves:) can now narrow its register with a constant filter:where: { status: ACTIVE }. This is what lets a register keep its history — cancelled and superseded rows no longer collide with the valid one and force the lookup to give up as ambiguous. Several conditions may be given and are ANDed; a status may be named by its seeded name rather than its id.Documentation
intent-assistant-guide.mdandcomponents/engine/engine-intent/CLAUDE.mdare updated in this PR. No matching docs-repo PR — the intent DSL reference lives in-tree.