['invalid'], ]); } $this->allocatedSync->syncForAgent($parent); $profile = $this->requireProfile($parent->id, 'parent_profile_required'); $available = max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit); $floor = $isNewChild ? 0 : max(0, $previousChildLimit); $maxAllowed = $floor + $available; if ($newChildLimit > $maxAllowed) { throw ValidationException::withMessages([ 'credit_limit' => ['exceeds_available'], ]); } } public function assertAllocationWithinParent(AgentNode $parent, int $additionalCredit): void { $this->allocatedSync->syncForAgent($parent); $profile = $this->requireProfile($parent->id, 'parent_profile_required'); $available = max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit); if ($additionalCredit > $available) { throw ValidationException::withMessages([ 'credit_limit' => ['exceeds_available'], ]); } } public function assertPlayerCreditWithinAgent(AgentNode $agent, int $playerCreditLimit): void { if ($playerCreditLimit < 0) { throw ValidationException::withMessages([ 'credit_limit' => ['invalid'], ]); } $this->assertPlayerCreditDeltaWithinAgent($agent, $playerCreditLimit); } public function assertPlayerCreditDeltaWithinAgent(AgentNode $agent, int $additionalCredit): void { if ($additionalCredit <= 0) { return; } AgentOverdueGuard::assertAgentMayGrantCredit((int) $agent->id); $this->allocatedSync->syncForAgent($agent); $profile = $this->requireProfile($agent->id, 'agent_profile_required'); $available = max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit); if ($additionalCredit > $available) { throw ValidationException::withMessages([ 'credit_limit' => ['exceeds_available'], ]); } } private function requireProfile(int $agentNodeId, string $missingKey): AgentProfile { $profile = AgentProfile::query()->where('agent_node_id', $agentNodeId)->first(); if ($profile === null) { throw ValidationException::withMessages([ 'credit_limit' => [$missingKey], ]); } return $profile; } }