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->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 — refactor to delegate checks to entity-level validator/service</li>
<li>src/Entity/AiContextItem.php — add validation/presave hooks or integrate constraint(s)</li>
<li>tests/src/Kernel/ — 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->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’s efficient with many items.</li>
</ul>
<p><strong>Risks & 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