feat: TR-069 CWMP state machine hardening + exchange viewer improvements

- TR-069 CWMP state machine hardening (14 phases): shared event utility,
  multi-event routing, GPN circuit breaker, IdleState GPN guard, expanded
  GPN fault codes, CriticalReadsState, ConfigEval GPN guard, task queue
  race condition fix, explicit error handling, terminal states
  (OperationalState/PartialOperationalState), consolidated reclassify,
  crash pattern detection, removed enqueueImmediate, state flow wiring
- Fix SOAP Fault not processed by state machine (soap-env:Fault detection)
- Fix GPN circuit breaker not persisting after session drops
- Fix CriticalReadsState GPV response parsing (xsi:type normalization)
- Fix CriticalReadsState not processing GPV response values
- Skip SPV when device is already compliant (ProvisioningCode check)
- Add Device.DeviceInfo.ModelName to critical reads GPV
- Fix TypeError on DeviceDetail parameters view (null guards)
- Log empty 204 responses in exchange trace UI
- TR-181 catalog: type corrections and new entries (+6640 lines)
- SequenceViewer: replace session-nav buttons with compact dropdown select
- SequenceViewer: fix CPE/ACS lifeline headers wrapping to 2 lines
- Add 20 unit tests for state machine fixes (SOAP Fault, GPN circuit
  breaker, CriticalReads, stale session, ConfigEval/ComplianceUpdate skips)

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
This commit is contained in:
Kevin L'HereecandContinue committed 2026-06-08 12:49:00 +00:00
1 parent dabe15839d
commit aad93911e4
35 files changed
+8593 -4497

No files matched your search

+5 -7
View File
@@ -89,33 +89,32 @@ export class CWMPTaskQueue extends EventEmitter {
/**
* Adds a task with callback (legacy-compatible API used by DeviceActionExecutor)
* Returns the CWMP XML envelope to send to the device
* Returns the CWMP XML envelope and task ID
*/
addTask(
deviceId: DeviceId,
taskType: string,
props: any,
callback: (result: TaskResult) => Promise<string>
): string {
): { xml: string; taskId: string } {
const cwmpTask = this.addCWMPTask(deviceId, taskType, props)
if (callback) {
this.taskCallbacks.set(cwmpTask.id, callback)
}
// Mark as SENT immediately so the manager correlates the response
// Mark as SENT immediately
if (cwmpTask.status === TaskStatus.PENDING) {
cwmpTask.status = TaskStatus.SENT
this.emit('taskSent', cwmpTask)
}
// Generate CWMP envelope
const builder = CWMPTaskQueue.task_map[taskType]
if (typeof builder !== 'function') {
logger.warn(`No CWMP builder found for task '${taskType}'`)
return ''
return { xml: '', taskId: cwmpTask.id }
}
return builder(props)
return { xml: builder(props), taskId: cwmpTask.id }
}
/**
@@ -168,7 +167,6 @@ export class CWMPTaskQueue extends EventEmitter {
if (cwmpTask.status === TaskStatus.PENDING) {
cwmpTask.status = TaskStatus.SENT
this.emit('taskSent', cwmpTask)
}
return { id: taskId, task: cwmpTask.type, props: cwmpTask.payload }