CCC: Refactor to move key form validations into entity-level validators / presave
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3574906. --> Reported by: [kristen pol](https://www.drupal.org/user/8389) Related to !86 >>> <p>[Tracker]<br> <strong>Update Summary: </strong>[One-line status update for stakeholders]<br> <strong>Check-in Date: </strong>MM/DD/YYYY<br> <strong>Blocked by: </strong>[#XXXXXX] (New issues on new lines)<br> <strong>Additional Collaborators: </strong> @username1, @username2<br> <em>Metadata is used by the <a href="https://www.drupalstarforge.ai/" title="AI Tracker">AI Tracker.</a> Docs and additional fields <a href="https://www.drupalstarforge.ai/ai-dashboard/docs" title="AI Issue Tracker Documentation">here</a>.</em><br> [/Tracker]</p> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p>Follow-up to:</p> <p><span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai_context/issues/3573713" title="Status: Closed (fixed)">#3573713: Full architecture review of CCC in prep for 1.0</a></span></p> <p><strong>Summary / goal</strong></p> <p>Enforce parent/subcontext rules (no self-parent, one-level-only nesting, parent-with-children cannot become subcontext) and global-item limits at the entity/persistence layer so programmatic saves (API, migrations, cron, tests) cannot bypass UI checks.</p> <p><strong>Why this matters</strong></p> <p>Currently important integrity and governance checks live only in AiContextItemForm::validateForm() which leaves programmatic writes (entity-&gt;save(), migrations, scripts) able to create invalid state. Moving the rules into entity-level validation prevents data corruption and keeps business rules centralized and testable.</p> <p><strong>Acceptance criteria</strong></p> <ul> <li>Kernel tests demonstrate programmatic saves that violate any of the rules are rejected (save raises exception or validation fails and entity is not persisted).</li> <li>AiContextItemForm still shows the same validation messages (form reuses the entity validator or service).</li> <li>Existing valid workflows (create/edit via UI, migrations that respect rules) continue to work unchanged.</li> <li>Tests cover: self-parent prevention, preventing an item with children from becoming a subcontext, preventing a parent that already has a parent (one-level only), and global-limit enforcement (respecting bundle default_status and limit behavior).</li> </ul> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <p><strong>Files (primary)</strong></p> <ul> <li>src/Form/AiContextItemForm.php &mdash; refactor to delegate checks to entity-level validator/service</li> <li>src/Entity/AiContextItem.php &mdash; add validation/presave hooks or integrate constraint(s)</li> <li>tests/src/Kernel/ &mdash; add kernel tests covering programmatic save failure cases</li> </ul> <p><strong>Recommended implementation approach (directional)</strong></p> <ol> <li>Extract the current form validation logic into a reusable validation service (e.g., AiContextItemValidator) that encapsulates: <ul> <li>isSelfParent(), parentHasParent(), hasChildren(), parentHasChildrenConstraint(), globalLimitCheck().</li> <ul></ul></ul></li> <li>Implement entity-level enforcement:</li> </ol><ul> <li>Preferred: add a Symfony/Drupal entity constraint (Constraint + ConstraintValidator) and attach it to the content entity (baseFieldDefinition-&gt;addConstraint or via entity validation service) so the standard entity validation API runs on save.</li> <li>Alternative: implement robust checks in AiContextItem::preSave() and throw a DomainException or prevent save if invalid (less flexible but quicker).</li> <ul> <li>Update AiContextItemForm::validateForm() to call the same validator service/constraint to build identical user-facing messages rather than duplicating logic.</li> <li>Ensure global-limit check references bundle settings (AiContextItemType::getDefaultStatus) and uses a performant count (COUNT query or cached counter) rather than loading all items.</li> <li>Add clear error messages for each violation so UI feedback remains actionable.</li> <p><strong>Subtasks</strong></p> <ul> <li>[Design] Decide Constraint vs preSave approach; define validator service API.</li> <li>[Extract] Move form validation code into AiContextItemValidator and unit-test service methods.</li> <li>[Entity] Implement entity-level constraint or preSave guard that invokes validator.</li> <li>[Form] Refactor form to call validator service and present identical messages.</li> <li>[Global count] Replace full loads with COUNT query or cached counter for global-limit check.</li> <li>[Tests] Add kernel tests for all invalid programmatic save cases and form behavior.</li> <li>[Docs] Note change in CONTRIBUTING.md and deprecate any direct \Drupal::entityTypeManager() usage in entities.</li> <li> </ul> <p><strong>Testing strategy</strong></p> <ul> <li>Kernel tests that attempt to create/update ai_context_item programmatically with invalid parent/subcontext configurations and assert save fails.</li> <li>Functional tests that submit invalid forms and confirm identical error messages.</li> <li>Performance test for global-limit COUNT path to ensure it&rsquo;s efficient with many items.</li> </ul> <p><strong>Risks &amp; mitigations</strong></p> <ul> <li>Risk: Breaking existing scripts that relied on looser constraints. Mitigate with documented migration path and clear error messages; provide a one-time migration/cleanup script.</li> <li>Risk: Throwing exceptions in presave may be unexpected to some callers. Mitigate by using Drupal validation API (preferred) that returns structured violations instead of hard exceptions.</li> </ul> <h3 id="summary-remaining-tasks">Target date or deadline</h3> <p>February 2026</p> <h3 id="summary-remaining-tasks">Remaining tasks</h3> <ul> <li>Review AI findings</li> <li>Decide if they are correct</li> <li>Decide if the proposed approach is best or propose alternative</li> <li>Update code</li> <li>Review and test</li> </ul> <h3 id="summary-ai-usage">AI usage (if applicable)</h3> <p>[x] AI Assisted Issue<br> This issue was generated with AI assistance, but was reviewed and refined by the creator.</p> <p>[ ] AI Assisted Code<br> This code was mainly generated by a human, with AI autocompleting or parts AI generated, but under full human supervision.</p> <p>[ ] AI Generated Code<br> This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.</p> <p>[ ] Vibe Coded<br> This code was generated by an AI and has only been functionally tested.</p> </ul></ul> > Related issue: [Issue #3573713](https://www.drupal.org/node/3573713)
issue