fix(intent): a generates: map key is validated against the target (#6953) - #6961
Merged
Merged
Conversation
) A `map:` entry has two ends and only one was checked. The value side has always been resolved against the source; the KEY side - the property the target receives - was never checked against the target at all. The generator pascal-cases the key and emits `target.<Key> = ...`, so a key the target does not declare is not a mis-mapping that shows up at run time: it is Java that does not compile, and client Java compiles as one registry-wide batch, so one bad key takes every module's beans down with it. The model parses clean, generates clean, and fails in generated code - the authored-but-broken class this module refuses everywhere else. `postings:` never had the hole - it checks its `map` keys against its `creates` target. This closes the asymmetry with the same message shape and the same case-insensitive match (the key is authored PascalCase, the target's field camelCase), applied at the three sites that mint a record from a map: `generates[].map` (target = `to:`), `generates[].items.map` (target = the items `to:`) and `schedules[].generate.map`. A CROSS-MODEL target (`uses:`) is exempt, the convention every cross-model reference follows: its property names live in the owner's `.model` and are resolved at generation time. The items map inherits that exemption, since a cross-model header implies a cross-model item target. Two in-repo fixtures turned out to be exactly the bug being closed - both mapped onto a LOCAL target that declared no such property, and both would have generated Java that does not compile. They now declare the property they meant. Every other fixture and the whole production-intent corpus (117 .intent documents) parse unchanged. Related: the back-reference map entry doubles as the at-most-once guard (#6711), so a mistyped key there silently broke the guard derivation too - this catches it at parse time. Co-Authored-By: Claude Opus 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.
Fixes #6953.
Problem
A
map:entry has two ends and only one was checked. The value side has always been resolved against the source (validateMapSource); the key side - the property the target receives - was never checked against the target at all.The generator pascal-cases the key and emits
target.<Key> = ..., so a key the target does not declare is not a mis-mapping that shows up at run time: it is Java that does not compile, and client Java compiles as one registry-wide batch, so one bad key takes every module's beans down with it. The model parses clean, generates clean, and fails in generated code - the authored-but-broken class this module refuses everywhere else.postings:never had the hole - it checks itsmapkeys against itscreatestarget.Fix
IntentParser.validateMapTarget(...), besidevalidateMapSource, with the same message shape and the same case-insensitive match (the key is authored PascalCase, the target's field camelCase). Applied at the three sites that mint a record from a map:generates[].mapto:generates [x] map [K] is not a field or to-one relation of [T]generates[].items.mapto:generates [x] items map [K] …schedules[].generate.mapto:schedule [x] generate map [K] …A cross-model target (
uses:) is exempt, the convention every cross-model reference follows: its property names live in the owner's.modeland are resolved at generation time. The items map inherits that exemption, since a cross-model header implies a cross-model item target.Two in-repo fixtures were the bug
Both mapped onto a LOCAL target that declared no such property, and both would have generated Java that does not compile:
GlueGeneratesTest.anEventDrivenGenerateWithoutABackReferenceFailsLoudly-map: { Note: note }onto aDeclarationwith nonotefield.IntentParserTest.scheduleGenerateChildrenValidate-map: { Person: id }onto aClaimwith noPersonrelation.They now declare the property they meant.
rejectsMapSourceThatIsNotASourcePropertyalso gained the target field it was missing, so it isolates the source-side error again.Verification
engine-intenttests pass.GeneratesIntentTest: the three sites plus the cross-model skip (which also covers the items map being exempt)..intentdocuments - every in-repo fixture plus the production-intent corpus - parse unchanged. No false positives.mvn formatter:formatproduces no reformatting.The authoring guide (
intent-assistant-guide.md) documented only the value side ofmap:; it now states the key-side rule and the cross-model exemption.Related: the back-reference map entry doubles as the at-most-once guard (#6711), so a mistyped key there silently broke the guard derivation too - this catches it at parse time.
🤖 Generated with Claude Code