65 lines
2.3 KiB
PHP
65 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\AiEditing\Safety;
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class AiAbuseEscalationService
|
|
{
|
|
public const REASON_CODE = 'abuse_escalation_threshold_reached';
|
|
|
|
/**
|
|
* @return array{type:string,count:int,threshold:int,escalated:bool,reason_code:?string}
|
|
*/
|
|
public function recordPromptBlock(int $tenantId, int $eventId, string $scope): array
|
|
{
|
|
return $this->record('prompt_block', $tenantId, $eventId, $scope);
|
|
}
|
|
|
|
/**
|
|
* @return array{type:string,count:int,threshold:int,escalated:bool,reason_code:?string}
|
|
*/
|
|
public function recordOutputBlock(int $tenantId, int $eventId, string $scope): array
|
|
{
|
|
return $this->record('output_block', $tenantId, $eventId, $scope);
|
|
}
|
|
|
|
/**
|
|
* @return array{type:string,count:int,threshold:int,escalated:bool,reason_code:?string}
|
|
*/
|
|
private function record(string $type, int $tenantId, int $eventId, string $scope): array
|
|
{
|
|
$threshold = max(1, (int) config('ai-editing.abuse.escalation_threshold_per_hour', 25));
|
|
$cooldownMinutes = max(1, (int) config('ai-editing.abuse.escalation_cooldown_minutes', 30));
|
|
$bucket = now()->format('YmdH');
|
|
|
|
$counterKey = sprintf('ai-editing:abuse:%s:tenant:%d:event:%d:hour:%s', $type, $tenantId, $eventId, $bucket);
|
|
Cache::add($counterKey, 0, now()->addHours(2));
|
|
$count = (int) Cache::increment($counterKey);
|
|
|
|
$escalated = $count >= $threshold;
|
|
if ($escalated) {
|
|
$cooldownKey = sprintf('ai-editing:abuse:%s:tenant:%d:event:%d:cooldown', $type, $tenantId, $eventId);
|
|
if (Cache::add($cooldownKey, 1, now()->addMinutes($cooldownMinutes))) {
|
|
Log::warning('AI abuse escalation threshold reached', [
|
|
'tenant_id' => $tenantId,
|
|
'event_id' => $eventId,
|
|
'type' => $type,
|
|
'count' => $count,
|
|
'threshold' => $threshold,
|
|
'scope_hash' => hash('sha256', $scope),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'type' => $type,
|
|
'count' => $count,
|
|
'threshold' => $threshold,
|
|
'escalated' => $escalated,
|
|
'reason_code' => $escalated ? self::REASON_CODE : null,
|
|
];
|
|
}
|
|
}
|