intent: a composition child can be personalReadOnly independently of its writable parent (#7340) - #7354
Open
delchev wants to merge 1 commit into
Open
intent: a composition child can be personalReadOnly independently of its writable parent (#7340)#7354delchev wants to merge 1 commit into
delchev wants to merge 1 commit into
Conversation
…its writable parent (#7340) `personalReadOnly` was declarable only alongside `personal: true`, and a composition child may not carry `personal:` at all - it inherits the scope through its parent. So a child's personal surface was writable exactly when its parent's was, which is the wrong coupling whenever a person authors a header whose lines an engine writes: base-vacations files the leave request itself (so the surface must be writable) while VacationDay rows are written only by VacationConsumeDelegate, each one charging a VacationEntitlement - and the generated VacationDayMyController therefore accepted a POST from the employee's own DRAFT naming any entitlement in the tenant, a colleague's account included, with the roll-up dutifully recording the charge. The page showed it as an Add button and a Fill Month action on the items panel. No other key reaches it: personalReadOnly on the parent closes the header the employee must author, `sensitive:` hides the value and still takes the write, and `immutableWhen` propagates to the child only for the statuses the parent declares final - a DRAFT parent is mutable by definition, which is precisely the window the abuse lives in. The key is now declarable on the CHILD's composition relation - the edge the scope actually travels - reading as: the scope still comes from the parent, the writes do not. The child's MyController answers 403 on create/update/delete and its personal pages render no write affordance; the parent's own personal surface keeps its header Save/Delete. Because the items panel lives on the MASTER's document page, the master carries a derived documentItemsReadOnly the document template gates Add / Fill Month / row delete / the item dialog's Save on, and each runtime child panel carries its own readOnly so its Add is dropped too. Deriving both in ModelParameterProcessor rather than emitting them means a hand-authored .edm carrying the child's attribute gets the same pages. Refused at parse wherever it would be carried nowhere - a dropped access declaration reads as a grant: on a relation that is neither personal nor a composition, on a second composition (only the first is the ownership edge), and on a child whose master has no personal surface to inherit. Verified: engine-intent + ide-template unit suites (1243 tests), a new InheritedPersonalReadOnlyIntentTest, the EDM and processor derivation tests, and - through the real templates, compiled and published - IntentEmissionCoverageIT (a new writable Timesheet master whose line and note children are see-only) plus ModelGenerationIT. formatter:validate green with the cache wiped; the javadoc release profile builds clean on both modules. Fixes #7340 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.
The cause
personalReadOnlywas declarable only alongsidepersonal: true, and a composition child may not carrypersonal:at all - it inherits the scope through its parent. So a child's personal surface was writable exactly when its parent's was.That is the wrong coupling whenever a person authors a header whose lines an engine writes.
base-vacationsfiles the leave request itself, soVacationRequest.Employeeispersonal: trueand the surface has to be writable;VacationDayrows are created ONLY byVacationConsumeDelegateon approval, and each one charges aVacationEntitlement. The generatedVacationDayMyControllertherefore accepted aPOSTfrom the employee's own DRAFT request naming anyEntitlementid in the tenant - a colleague's leave account included - and the roll-up dutifully recorded the charge. The personal document page showed it as an Add button and a Fill Month action on the items panel.No other key reaches it:
personalReadOnlyon the parent closes the header the employee must author.sensitive: truehides values on read; the write is still accepted.immutableWhendoes propagate to the child (requireMasterMutable), but only for the statuses the parent declares final - and a DRAFT parent is mutable by definition, which is precisely the window this lives in.The shape recurs wherever a person authors a header and the engine writes the lines: an expense claim whose reimbursement lines a delegate computes, an order whose allocation rows a settlement writes.
The change
The key is now declarable on the child's composition relation - the edge the scope actually travels:
Read as: the scope still comes from the parent; the writes do not.
<Child>MyController's create / update / delete answer 403 and its personal pages render no write affordance, while the parent's own personal surface keeps its header Save/Delete. The power controller is untouched, and so is every engine writer, which goes through the repository.Two derivations carry it to the pages, both in
ModelParameterProcessorrather than emitted into the model, so a hand-authored.edmcarrying the child's attribute gets the same pages:inheritPersonalScopeORs the child's ownrelationshipPersonalReadOnlyinto itspersonalReadOnly.documentItemsReadOnlythat the document template gates Add / Fill Month / the per-row Delete / the item dialog's Save on. Each runtime child panel carries its ownreadOnly, so a see-only non-item child loses its Add too.Refused at parse wherever the key would be carried nowhere - a dropped access declaration reads as a grant: on a relation that is neither
personal:nor a composition, on a second composition (only the first is the ownership edge, mirroringwhenMasterDeleted), and on a child whose master has no personal surface to inherit. A model that does not use the key generates byte-identically.Verified
engine-intent+ide-templateunit suites green (1243 tests), including the newInheritedPersonalReadOnlyIntentTest(6 cases), the EDM emission test and threeModelParameterProcessorTestderivation cases.IntentEmissionCoverageIT- through the real templates, javac'd and published: a new writableTimesheetdocument master whoseTimesheetLine(items) andTimesheetNote(panel) children declare the key. Asserts the child's controller 403s and emits norepository.save, the master's header keeps Save/Delete, the items Add / per-row Delete / dialog Save are gone while a line still opens for reading, the panel descriptor carriesreadOnly: true, and a writable child's panel still carriesreadOnly: false.ModelGenerationITgreen.mvn formatter:validategreen with the formatter cache wiped; thereleaseprofile's javadoc builds clean on both changed modules.Not verified: no browser run - the UI half is asserted on the generated markup and page JS.
Docs
components/engine/engine-intent/CLAUDE.mdand the intent assistant guide updated in this PR.0018-child-scoped-read-only.md).Fixes #7340
🤖 Generated with Claude Code