ShapeClassifier::classify($ticket) returns 'A'|'B'|'C' per policy v1: - (severity=critical OR priority=urgent) → A - has tag in ['security','crypto','core'] → A - (severity=major OR priority=high) → B - everything else → C ProfileSelector::pickFor($ticket) walks AgentProfile::active(), matches capability tags case-insensitively against ticket.tags: - Class A: cheapest matching profile (cost_class alphabetic order) - Class B: any active profile with quota_headroom_pct >= 25 - Class C: deterministic round-robin via last_dispatched_at Pest Unit tests cover Good (matching profile picked), Bad (no match → null), Ugly (all profiles disabled → null), plus class A/B headroom gating + class C round-robin determinism. Codex note: php -l clean; pest skipped — no vendor/ at this repo root (downstream lab/lthn.ai owns composer install). Closes tasks.lthn.sh/view.php?id=826 Co-authored-by: Codex <noreply@openai.com>
57 lines
1.8 KiB
PHP
57 lines
1.8 KiB
PHP
<?php
|
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Core\Mod\Agentic\Services\ShapeClassifier;
|
|
|
|
test('ShapeClassifier_classify_Good_returns_A_for_critical_urgent_and_escalation_tags', function (): void {
|
|
$classifier = new ShapeClassifier;
|
|
|
|
expect($classifier->classify([
|
|
'severity' => 'critical',
|
|
'priority' => 'normal',
|
|
'tags' => [],
|
|
]))->toBe(ShapeClassifier::CLASS_A)
|
|
->and($classifier->classify((object) [
|
|
'severity' => 'minor',
|
|
'priority' => 'urgent',
|
|
'tags' => [],
|
|
]))->toBe(ShapeClassifier::CLASS_A)
|
|
->and($classifier->classify([
|
|
'severity' => 'minor',
|
|
'priority' => 'low',
|
|
'tags' => ['security', 'bugfix'],
|
|
]))->toBe(ShapeClassifier::CLASS_A);
|
|
});
|
|
|
|
test('ShapeClassifier_classify_Bad_returns_B_for_major_or_high_without_A_signals', function (): void {
|
|
$classifier = new ShapeClassifier;
|
|
|
|
expect($classifier->classify([
|
|
'severity' => 'major',
|
|
'priority' => 'normal',
|
|
'tags' => ['dispatch'],
|
|
]))->toBe(ShapeClassifier::CLASS_B)
|
|
->and($classifier->classify((object) [
|
|
'severity' => 'minor',
|
|
'priority' => 'high',
|
|
'tags' => ['review'],
|
|
]))->toBe(ShapeClassifier::CLASS_B);
|
|
});
|
|
|
|
test('ShapeClassifier_classify_Ugly_returns_C_when_no_escalation_signals_exist', function (): void {
|
|
$classifier = new ShapeClassifier;
|
|
|
|
expect($classifier->classify([
|
|
'severity' => 'minor',
|
|
'priority' => 'normal',
|
|
'tags' => ['dispatch'],
|
|
]))->toBe(ShapeClassifier::CLASS_C)
|
|
->and($classifier->classify([
|
|
'severity' => null,
|
|
'priority' => null,
|
|
'tags' => [],
|
|
]))->toBe(ShapeClassifier::CLASS_C);
|
|
});
|