agent/php/tests/Feature/Mod/Agent/CompleteTaskFoundationTest.php
Snider 429d1c0897 feat(agent/agentic): RFC foundation — atomic CompleteTask + credit ledger reconcile
Foundation slice for Mantis #841 php/Mod/Agent RFC implementation:

* CompleteTask now wraps in DB::transaction with idempotent credit awards
  and safe current_task_id clearing
* Credits/{Award,GetBalance,GetCreditHistory} updated for agent_id +
  fleet_task_id ledger support and richer balance totals
* GenerateCommand canonical agentic:generate wiring; legacy duplicate
  no longer registered
* Boot wires brain:clean / brain:prune / brain:reindex
* EmbedMemory exits early when memory already indexed
* 3 follow-on fleet migrations reconcile fleet_nodes pointer column,
  fleet_tasks/credit_entries fk/index hygiene, fleet+credit constraints
* 4 foundation tests under php/tests/Feature/Mod/Agent/

php -l clean on all modified files. pest unrunnable in sandbox (no vendor/).

Foundation slice only: remaining model/action parity, full MCP tool/
service sweep, fleet controller auth-context, and 41-tool/45-action
surface left for follow-up tickets.

Co-authored-by: Codex <noreply@openai.com>
Closes tasks.lthn.sh/view.php?id=841
2026-04-25 20:59:38 +01:00

72 lines
2.2 KiB
PHP

<?php
// SPDX-License-Identifier: EUPL-1.2
declare(strict_types=1);
use Core\Mod\Agentic\Actions\Fleet\CompleteTask;
use Core\Mod\Agentic\Models\CreditEntry;
use Core\Mod\Agentic\Models\FleetNode;
use Core\Mod\Agentic\Models\FleetTask;
test('agent foundation complete task is atomic and idempotent for credits', function (): void {
$workspace = createWorkspace();
$node = FleetNode::query()->create([
'workspace_id' => $workspace->id,
'agent_id' => 'charon',
'platform' => 'linux',
'status' => FleetNode::STATUS_BUSY,
'registered_at' => now()->subMinutes(10),
'last_heartbeat_at' => now()->subMinute(),
]);
$task = FleetTask::query()->create([
'workspace_id' => $workspace->id,
'fleet_node_id' => $node->id,
'repo' => 'dappco.re/go/agent',
'branch' => 'dev',
'task' => 'Complete the foundation slice',
'status' => FleetTask::STATUS_IN_PROGRESS,
'started_at' => now()->subMinutes(5),
]);
$node->update(['current_task_id' => $task->id]);
$completed = CompleteTask::run(
$workspace->id,
'charon',
$task->id,
['status' => 'completed'],
[['severity' => 'medium']],
['files_changed' => 3],
['summary' => 'Foundation delivered'],
);
CompleteTask::run(
$workspace->id,
'charon',
$task->id,
['status' => 'completed'],
[['severity' => 'medium']],
['files_changed' => 3],
['summary' => 'Foundation delivered'],
);
$creditEntry = CreditEntry::query()
->where('workspace_id', $workspace->id)
->where('fleet_node_id', $node->id)
->where('fleet_task_id', $task->id)
->first();
expect($completed->status)->toBe(FleetTask::STATUS_COMPLETED)
->and($node->fresh()->status)->toBe(FleetNode::STATUS_ONLINE)
->and($node->fresh()->current_task_id)->toBeNull()
->and($creditEntry)->not->toBeNull()
->and($creditEntry?->agent_id)->toBe('charon')
->and(CreditEntry::query()
->where('workspace_id', $workspace->id)
->where('fleet_node_id', $node->id)
->where('fleet_task_id', $task->id)
->count())->toBe(1);
});