diff --git a/.kiro/steering/debug-reclassification.md b/.kiro/steering/debug-reclassification.md new file mode 100644 index 0000000..5ce378d --- /dev/null +++ b/.kiro/steering/debug-reclassification.md @@ -0,0 +1,137 @@ +# Debug: Reclassification Properties → Parameters + +## Contexte du problème + +Les paramètres `Device.ManagementServer.PeriodicInformInterval` et `Device.DeviceInfo.ProvisioningCode` apparaissent comme **properties** (read-only) dans l'interface alors qu'ils devraient être des **parameters** (writable). + +Le catalogue `config/tr181-catalog.json` les marque bien comme `writable: true`. +La fonction `reclassifyDeviceFromCatalog()` dans `app/services/parameter_writability.ts` est censée les déplacer de properties vers parameters quand `gpnStatus === 'unsupported'`. + +Le dernier log de debug montre : `[Reclassify] properties=9, reclassified=0, catalogSize=2953` — la fonction est appelée mais ne reclassifie rien malgré les paths présents dans le catalogue. + +## Fichiers impliqués + +- `app/services/parameter_writability.ts` — contient `reclassifyDeviceFromCatalog()` et `normalizePath()` +- `app/services/cwmp/device_action_executor.ts` — appelle la reclassification quand GPN échoue +- `app/services/cwmp/main_state_machine.ts` — `CompletedState` appelle aussi la reclassification +- `app/controllers/cwmp_controller.ts` — `handleInform()` appelle la reclassification si `gpnStatus === 'unsupported'` +- `app/domain/device/device.ts` — `updateFromValues()` stocke les nouveaux params comme properties par défaut +- `config/tr181-catalog.json` — référentiel de writabilité TR-181 + +## Procédure de test complète + +### 1. Faire la modification de code + +Modifier le fichier concerné, puis vérifier la compilation : + +```bash +npx tsc --noEmit +``` + +### 2. Build et relance Docker + +```bash +docker compose build app +docker compose up -d app +``` + +Attendre que l'app soit healthy : + +```bash +docker compose logs app --tail 5 +``` + +### 3. Supprimer le device Sagemcom de l'ACS + +```bash +curl -s -X DELETE http://localhost:8080/api/v1/devices/2e933565b8fc09f34f42531e +``` + +### 4. Supprimer le device du simulateur (home-device-simulator) + +```bash +curl -s -X DELETE "http://localhost:9898/api/v1/device-simulator/delete-devices" \ + -H 'Content-Type: application/json' \ + -d '["F8:B7:FF:00:11:22"]' +``` + +### 5. Recréer le device dans le simulateur + +```bash +curl -s -X PUT "http://localhost:9898/api/v1/device-simulator/create-devices?noOfDevices=1&autoGenerate=false&ipAddressType=ipv4" \ + -H 'Content-Type: application/json' \ + -d '{ + "manufacturer": "Sagemcom", + "oui": "F8B7FF", + "productClass": "Livebox 4", + "serialNumber": "LB4-TEST-001", + "macaddress": "F8:B7:FF:00:11:22", + "parameters": [ + {"type": "Manufacturer", "model": "Device.DeviceInfo.Manufacturer", "value": "Sagemcom"}, + {"type": "ManufacturerOUI", "model": "Device.DeviceInfo.ManufacturerOUI", "value": "F8B7FF"}, + {"type": "ModelName", "model": "Device.DeviceInfo.ModelName", "value": "Livebox 4"}, + {"type": "ProductClass", "model": "Device.DeviceInfo.ProductClass", "value": "Livebox 4"}, + {"type": "SerialNumber", "model": "Device.DeviceInfo.SerialNumber", "value": "LB4-TEST-001"}, + {"type": "HardwareVersion", "model": "Device.DeviceInfo.HardwareVersion", "value": "SR40_sip-fr-4.62.0"}, + {"type": "SoftwareVersion", "model": "Device.DeviceInfo.SoftwareVersion", "value": "SR40_sip-fr-4.62.0"} + ] + }' +``` + +### 6. Attendre l'Inform (30s max) puis vérifier + +```bash +sleep 30 +``` + +Vérifier la classification : + +```bash +curl -s "http://localhost:8080/api/v1/devices/2e933565b8fc09f34f42531e" | \ + python3 -c " +import sys,json +d=json.load(sys.stdin) +print('gpnStatus:', d.get('gpnStatus')) +props = [p['name'] for p in d.get('properties',[]) if 'PeriodicInform' in p['name'] or 'ProvisioningCode' in p['name']] +params = [p['name'] for p in d.get('parameters',[]) if 'PeriodicInform' in p['name'] or 'ProvisioningCode' in p['name']] +print('Dans properties (MAUVAIS):', props) +print('Dans parameters (BON):', params) +" +``` + +### 7. Regarder les logs de reclassification + +```bash +docker compose logs app --tail 50 2>&1 | grep -i "reclassify\|Reclassify" +``` + +## Résultat attendu + +``` +gpnStatus: unsupported +Dans properties (MAUVAIS): [] +Dans parameters (BON): ['Device.ManagementServer.PeriodicInformInterval', 'Device.DeviceInfo.ProvisioningCode'] +``` + +## Identifiants du device de test + +| Champ | Valeur | +|-------|--------| +| ACS identifier | `2e933565b8fc09f34f42531e` | +| Manufacturer | Sagemcom | +| OUI | F8B7FF | +| Product Class | Livebox 4 | +| Serial Number | LB4-TEST-001 | +| MAC (simulateur) | F8:B7:FF:00:11:22 | +| Data Model | tr-181 | + +## API du simulateur + +- Base URL : `http://localhost:9898/api/v1/device-simulator` +- Swagger : `http://localhost:9898/swagger/` +- Suppression par MAC : `DELETE /delete-devices` body `["MAC"]` +- Création : `PUT /create-devices?noOfDevices=1&autoGenerate=false&ipAddressType=ipv4` + +## Trace TR-069 + +Le device est déjà dans la config de trace (`data/tr069-trace.json` dans le volume Docker `app_tr069_data`). Les logs SOAP sont visibles sur `http://localhost:8080/tr069-trace`. diff --git a/app/services/cwmp/configuration_evaluation_state.ts b/app/services/cwmp/configuration_evaluation_state.ts index 58392fe..953db7e 100644 --- a/app/services/cwmp/configuration_evaluation_state.ts +++ b/app/services/cwmp/configuration_evaluation_state.ts @@ -108,7 +108,8 @@ export class ConfigurationEvaluationState { ) return { response } } else if (this.inventoryQueue.length > 0) { - // GPN unsupported — clear inventory queue and skip to next phase + // GPN unsupported — skip inventory (GPV on object paths also fails on most simulators) + // Parameters will be discovered via Inform values and SPV responses logger.debug({ identifier: context.device.identifier, skipped: this.inventoryQueue.length }, '[ConfigEval] Skipping inventory (GPN unsupported)') this.inventoryQueue = [] } diff --git a/app/services/cwmp/device_action_executor.ts b/app/services/cwmp/device_action_executor.ts index c8294f5..7a1009f 100644 --- a/app/services/cwmp/device_action_executor.ts +++ b/app/services/cwmp/device_action_executor.ts @@ -98,6 +98,9 @@ export class DeviceActionExecutor { const list = resp?.ParameterList?.ParameterValueStruct if (Array.isArray(list) && list.length > 0) { device.updateFromValues(list) + if (device.gpnStatus === 'unsupported') { + reclassifyDeviceFromCatalog(device) + } await this.persistDevice(device) } } catch (e) { @@ -235,6 +238,9 @@ export class DeviceActionExecutor { async (result: TaskResult) => { if (result.status === ResultStatus.Success) { device.updateFromValues(pvStructs as any) + if (device.gpnStatus === 'unsupported') { + reclassifyDeviceFromCatalog(device) + } await this.persistDevice(device) return await callback(true) } @@ -396,6 +402,9 @@ export class DeviceActionExecutor { async (result: TaskResult) => { if (result.status === ResultStatus.Success) { device.updateFromValues(pvStructs as any) + if (device.gpnStatus === 'unsupported') { + reclassifyDeviceFromCatalog(device) + } await this.persistDevice(device) device.tmpPC = '' return await callback(true) diff --git a/app/services/cwmp/main_state_machine.ts b/app/services/cwmp/main_state_machine.ts index a68edb7..5925db8 100644 --- a/app/services/cwmp/main_state_machine.ts +++ b/app/services/cwmp/main_state_machine.ts @@ -8,6 +8,7 @@ import CommandModel from '../../models/command.js' import { ModeType } from '../../domain/shared/mode.js' import logger from '@adonisjs/core/services/logger' import { ConfigurationEvaluationState } from './configuration_evaluation_state.js' +import { reclassifyDeviceFromCatalog } from '../parameter_writability.js' // ─── Interfaces ──────────────────────────────────────────────────────────────── @@ -277,6 +278,11 @@ class CompletedState extends DeviceState { name = 'completed' async handle(context: DeviceContext): Promise { + // Reclassify properties → parameters if GPN is unsupported + if (context.device.gpnStatus === 'unsupported') { + reclassifyDeviceFromCatalog(context.device) + } + // Persist device on completion try { const snapshot = context.device.toSnapshot()