Skip to content

Commit c6a80fe

Browse files
committed
fix(orchestration): corrigir comentários do code review no Cognitive Load Balancer
- Validação de ID duplicado ao submeter tasks - Guards de transição de estado (COMPLETED/FAILED são terminais) - Validação do parâmetro profile em registerAgent - Processar fila ao registrar novo agente - Persistência async de métricas com tratamento de erro - Emissão de eventos apenas em transições reais de status
1 parent a35f714 commit c6a80fe

5 files changed

Lines changed: 306 additions & 739 deletions

File tree

.aios-core/core/orchestration/cognitive-load-balancer.js

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,26 @@ class CognitiveLoadBalancer extends EventEmitter {
201201
* @param {number} [profile.processingSpeed=1.0] - Processing speed multiplier
202202
* @returns {Object} Registered agent profile
203203
* @throws {Error} If agentId is not a non-empty string
204+
* @throws {Error} If profile is provided but is not an object
204205
*/
205206
registerAgent(agentId, profile = {}) {
206207
if (!agentId || typeof agentId !== 'string') {
207208
throw new Error('agentId must be a non-empty string');
208209
}
209210

210-
const agentProfile = createAgentProfile(agentId, profile);
211+
// Validate profile overrides must be object or undefined
212+
if (profile !== undefined && profile !== null && typeof profile !== 'object') {
213+
throw new Error('profile must be an object or undefined');
214+
}
215+
216+
const agentProfile = createAgentProfile(agentId, profile || {});
211217
this.agents.set(agentId, agentProfile);
212218

213219
this.emit('agent:registered', { agentId, profile: agentProfile });
220+
221+
// Process queue when new agent registers so queued tasks get assigned
222+
this._processQueue();
223+
214224
return agentProfile;
215225
}
216226

@@ -261,13 +271,21 @@ class CognitiveLoadBalancer extends EventEmitter {
261271
* @param {number} [taskInput.complexity=5] - Complexity 1-10
262272
* @param {string[]} [taskInput.requiredSpecialties=[]] - Required specialties
263273
* @returns {Object} Submission result with taskId and assignedTo
274+
* @throws {Error} If task input is not a non-null object
275+
* @throws {Error} If task with same ID already exists
264276
*/
265277
submitTask(taskInput) {
266278
if (!taskInput || typeof taskInput !== 'object') {
267279
throw new Error('Task must be a non-null object');
268280
}
269281

270282
const task = createTask(taskInput);
283+
284+
// Reject duplicate task IDs
285+
if (this.tasks.has(task.id)) {
286+
throw new Error(`Task '${task.id}' already exists`);
287+
}
288+
271289
this.tasks.set(task.id, task);
272290
this.metrics.totalSubmitted++;
273291

@@ -308,13 +326,22 @@ class CognitiveLoadBalancer extends EventEmitter {
308326
* @param {string} agentId - Target agent
309327
* @returns {Object} Assignment result
310328
* @throws {Error} If task or agent not found
329+
* @throws {Error} If task is already completed or failed
311330
*/
312331
assignTask(taskId, agentId) {
313332
const task = this.tasks.get(taskId);
314333
if (!task) {
315334
throw new Error(`Task '${taskId}' not found`);
316335
}
317336

337+
// Enforce task state transitions - cannot assign completed/failed tasks
338+
if (task.status === TaskStatus.COMPLETED) {
339+
throw new Error(`Task '${taskId}' is already completed`);
340+
}
341+
if (task.status === TaskStatus.FAILED) {
342+
throw new Error(`Task '${taskId}' is already failed`);
343+
}
344+
318345
const agent = this.agents.get(agentId);
319346
if (!agent) {
320347
throw new Error(`Agent '${agentId}' not found`);
@@ -348,13 +375,22 @@ class CognitiveLoadBalancer extends EventEmitter {
348375
* @param {*} [result=null] - Task result
349376
* @returns {Object} Completion info
350377
* @throws {Error} If task not found
378+
* @throws {Error} If task is already completed or failed
351379
*/
352-
completeTask(taskId, result = null) {
380+
async completeTask(taskId, result = null) {
353381
const task = this.tasks.get(taskId);
354382
if (!task) {
355383
throw new Error(`Task '${taskId}' not found`);
356384
}
357385

386+
// Enforce task state transitions
387+
if (task.status === TaskStatus.COMPLETED) {
388+
throw new Error(`Task '${taskId}' is already completed`);
389+
}
390+
if (task.status === TaskStatus.FAILED) {
391+
throw new Error(`Task '${taskId}' is already failed`);
392+
}
393+
358394
task.status = TaskStatus.COMPLETED;
359395
task.completedAt = Date.now();
360396
task.result = result;
@@ -375,8 +411,8 @@ class CognitiveLoadBalancer extends EventEmitter {
375411
// Try to process queue after freeing capacity
376412
this._processQueue();
377413

378-
// Persist metrics
379-
this._persistMetrics();
414+
// Await metrics persistence instead of fire-and-forget
415+
await this._persistMetrics();
380416

381417
return {
382418
taskId,
@@ -391,13 +427,22 @@ class CognitiveLoadBalancer extends EventEmitter {
391427
* @param {string|Error} [error='Unknown error'] - Error description
392428
* @returns {Object} Failure info
393429
* @throws {Error} If task not found
430+
* @throws {Error} If task is already completed or failed
394431
*/
395-
failTask(taskId, error = 'Unknown error') {
432+
async failTask(taskId, error = 'Unknown error') {
396433
const task = this.tasks.get(taskId);
397434
if (!task) {
398435
throw new Error(`Task '${taskId}' not found`);
399436
}
400437

438+
// Enforce task state transitions
439+
if (task.status === TaskStatus.COMPLETED) {
440+
throw new Error(`Task '${taskId}' is already completed`);
441+
}
442+
if (task.status === TaskStatus.FAILED) {
443+
throw new Error(`Task '${taskId}' is already failed`);
444+
}
445+
401446
const errorMessage = error instanceof Error ? error.message : String(error);
402447
task.status = TaskStatus.FAILED;
403448
task.completedAt = Date.now();
@@ -416,8 +461,8 @@ class CognitiveLoadBalancer extends EventEmitter {
416461
// Try to process queue after freeing capacity
417462
this._processQueue();
418463

419-
// Persist metrics
420-
this._persistMetrics();
464+
// Await metrics persistence instead of fire-and-forget
465+
await this._persistMetrics();
421466

422467
return {
423468
taskId,
@@ -727,6 +772,9 @@ class CognitiveLoadBalancer extends EventEmitter {
727772
* @private
728773
*/
729774
_assignTaskToAgent(task, agent) {
775+
// Save previous status for transition-only events
776+
const previousStatus = agent.status;
777+
730778
task.assignedTo = agent.id;
731779
task.status = TaskStatus.ASSIGNED;
732780
task.startedAt = Date.now();
@@ -737,9 +785,9 @@ class CognitiveLoadBalancer extends EventEmitter {
737785
this._updateAgentStatus(agent);
738786
this.emit('task:assigned', { taskId: task.id, agentId: agent.id });
739787

740-
// Check if agent became overloaded
741-
const loadPct = agent.maxLoad > 0 ? (agent.currentLoad / agent.maxLoad) * 100 : 100;
742-
if (loadPct >= OVERLOAD_THRESHOLD) {
788+
// Only emit agent:overloaded on actual status transition
789+
if (agent.status === AgentStatus.OVERLOADED && previousStatus !== AgentStatus.OVERLOADED) {
790+
const loadPct = agent.maxLoad > 0 ? (agent.currentLoad / agent.maxLoad) * 100 : 100;
743791
this.emit('agent:overloaded', { agentId: agent.id, load: loadPct });
744792
}
745793
}
@@ -751,6 +799,9 @@ class CognitiveLoadBalancer extends EventEmitter {
751799
* @private
752800
*/
753801
_removeTaskFromAgent(agent, taskId) {
802+
// Save previous status for transition-only events
803+
const previousStatus = agent.status;
804+
754805
const idx = agent.activeTasks.indexOf(taskId);
755806
if (idx !== -1) {
756807
agent.activeTasks.splice(idx, 1);
@@ -763,9 +814,10 @@ class CognitiveLoadBalancer extends EventEmitter {
763814

764815
this._updateAgentStatus(agent);
765816

766-
// Check if agent became available again
767-
const loadPct = agent.maxLoad > 0 ? (agent.currentLoad / agent.maxLoad) * 100 : 100;
768-
if (loadPct < OVERLOAD_THRESHOLD && agent.status !== AgentStatus.OFFLINE) {
817+
// Only emit agent:available on transition FROM overloaded
818+
if (agent.status !== AgentStatus.OVERLOADED && agent.status !== AgentStatus.OFFLINE
819+
&& previousStatus === AgentStatus.OVERLOADED) {
820+
const loadPct = agent.maxLoad > 0 ? (agent.currentLoad / agent.maxLoad) * 100 : 100;
769821
this.emit('agent:available', { agentId: agent.id, load: loadPct });
770822
}
771823
}
@@ -914,8 +966,9 @@ class CognitiveLoadBalancer extends EventEmitter {
914966

915967
await fs.mkdir(metricsDir, { recursive: true });
916968
await fs.writeFile(metricsPath, JSON.stringify(this.getMetrics(), null, 2), 'utf8');
917-
} catch {
918-
// Silently ignore persistence errors in production
969+
} catch (err) {
970+
// Log persistence errors with context instead of silently ignoring
971+
console.error(`Failed to persist load balancer metrics: ${err.message}`);
919972
}
920973
}
921974
}

0 commit comments

Comments
 (0)