services/api/src/modules/reconciliation/deletion-inventory.classify.js
Overview
Este módulo implementa la clasificación pura del inventario de eliminaciones de Requirements. Recibe evidencia ya reunida por deletion-inventory.service.js y devuelve decisiones deterministas y explicables, sin acceder a MongoDB, ImageKit ni otros servicios .
Su papel es separar la lectura de evidencia de la decisión de clasificación. Las pruebas del inventario confirman que este flujo permanece de solo lectura, sin escrituras, sin $merge ni $out, y sin imports prohibidos . Las pruebas unitarias también verifican que authorizesExecution es siempre false fileciteturn0file6turn0file8turn0file11turn0file12.
[!IMPORTANT] Ninguna decisión de este módulo autoriza ejecución.
AUTO_SAFEsolo significa que una recuperación futura podría ser segura; no habilita la acción hoy .
How it works
flowchart TD
A[Evidence already gathered] --> B[Select classifier]
B --> C[classifyPendingRequirement]
B --> D[classifyMediaClaim]
B --> E[classifyConversationReference]
B --> F[classifySharedFile]
C --> G[Decision object]
D --> G
E --> G
F --> G
G --> H[requiresApproval from suggestedAction]
G --> I[authorizesExecution false]
El módulo sigue un patrón estable. Cada clasificador valida la evidencia, detecta ausencias o contradicciones, y devuelve un objeto uniforme. Ese objeto siempre incluye una clasificación, un código de motivo y una acción sugerida, pero nunca habilita escritura fileciteturn0file0turn0file3.
[!NOTE]
deletion-inventory.service.jsprepara la evidencia por secciones y llama a estas funciones puras para decidir el resultado final de cada candidato .
Key components
{
"title": "CLASSIFICATIONS",
"content": "Objeto congelado con los estados AUTO_SAFE, VERIFY_FIRST, MANUAL_ONLY, NO_ACTION y UNRESOLVED.",
"type": "enum",
"filePath": "services/api/src/modules/reconciliation/deletion-inventory.classify.js",
"badges": [
"public"
]
}
{
"title": "CLAIM_STATES",
"content": "Lista congelada con los estados permitidos para una MediaFileClaim: active, deleting, erasing y deleted.",
"type": "enum",
"filePath": "services/api/src/modules/reconciliation/deletion-inventory.classify.js",
"badges": [
"public"
]
}
{
"title": "classifyPendingRequirement",
"content": "Clasifica Requirements con deletion.status = pending usando evidencia ya reunida. Devuelve una decisi\u00f3n explicable y no autoriza ejecuci\u00f3n.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/deletion-inventory.classify.js",
"badges": [
"public"
]
}
{
"title": "classifyMediaClaim",
"content": "Clasifica una asignaci\u00f3n MediaFileClaim seg\u00fan su estado, su holder y las referencias del Listing. Nunca autoriza una escritura.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/deletion-inventory.classify.js",
"badges": [
"public"
]
}
{
"title": "classifyConversationReference",
"content": "Clasifica una Conversation con activeRequirementId y decide si la referencia al Requirement es v\u00e1lida, pendiente de reparaci\u00f3n o requiere revisi\u00f3n humana.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/deletion-inventory.classify.js",
"badges": [
"public"
]
}
{
"title": "classifySharedFile",
"content": "Clasifica un archivo compartido por varios Listings. Solo informa sobre la evidencia observada; no prueba ausencia global de referencias.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/deletion-inventory.classify.js",
"badges": [
"public"
]
}
Data & fields
La salida de todas las funciones comparte la misma forma. La evidencia cambia según el clasificador, pero el contrato de respuesta es uniforme.
Resultado de decisión
[
{
"name": "classification",
"type": "string",
"required": true,
"description": "Uno de AUTO_SAFE, VERIFY_FIRST, MANUAL_ONLY, NO_ACTION o UNRESOLVED."
},
{
"name": "reasonCode",
"type": "string",
"required": true,
"description": "Código estable y explicable que identifica la regla aplicada."
},
{
"name": "suggestedAction",
"type": "string",
"required": true,
"description": "Acción sugerida para el siguiente paso. No ejecuta cambios."
},
{
"name": "requiresApproval",
"type": "boolean",
"required": true,
"description": "True cuando suggestedAction no es none."
},
{
"name": "authorizesExecution",
"type": "boolean",
"required": true,
"description": "Siempre false en este módulo."
}
]
CLASSIFICATIONS
AUTO_SAFE— Hay evidencia suficiente para considerar una recuperación automática futura.VERIFY_FIRST— Falta una comprobación adicional antes de cualquier acción.MANUAL_ONLY— Hace falta una decisión humana caso por caso.NO_ACTION— El estado es coherente y no hay nada que recuperar.UNRESOLVED— La evidencia es incompleta o la política aún no está decidida.
CLAIM_STATES
activedeletingerasingdeleted
[!WARNING]
erasingnunca puede producirAUTO_SAFE. El archivo todavía puede estar en una operación activa o inconclusa fileciteturn0file0turn0file12.
classifyPendingRequirement
[
{
"name": "stillPending",
"type": "boolean",
"required": true,
"description": "Indica si el Requirement sigue pendiente al final del barrido."
},
{
"name": "ownerExists",
"type": "boolean",
"required": true,
"description": "Indica si el propietario persistido sigue existiendo."
},
{
"name": "accountStatus",
"type": "string",
"required": true,
"description": "Estado de la cuenta del propietario. Solo active permite continuar."
},
{
"name": "conversationRefs",
"type": "object",
"required": true,
"description": "Conteo de referencias a Conversations observadas en el inventario.",
"children": [
{
"name": "own",
"type": "number",
"required": true,
"description": "Referencias propias observadas."
},
{
"name": "foreign",
"type": "number",
"required": true,
"description": "Referencias de otros Listing observadas."
}
]
},
{
"name": "reservations",
"type": "object",
"required": true,
"description": "Estado agregado de reservas asociadas al Requirement.",
"children": [
{
"name": "total",
"type": "number",
"required": true,
"description": "Total de reservas observadas."
},
{
"name": "live",
"type": "number",
"required": true,
"description": "Reservas todavía vigentes."
}
]
},
{
"name": "attachInProgress",
"type": "object",
"required": true,
"description": "Estado agregado de asociaciones en curso.",
"children": [
{
"name": "total",
"type": "number",
"required": true,
"description": "Total de asociaciones observadas."
},
{
"name": "live",
"type": "number",
"required": true,
"description": "Asociaciones todavía vigentes."
}
]
},
{
"name": "fileClaimStates",
"type": "array",
"required": true,
"description": "Estados observados de las MediaFileClaim asociadas."
}
]
classifyMediaClaim
[
{
"name": "state",
"type": "string",
"required": true,
"description": "Estado de la MediaFileClaim. Debe pertenecer a CLAIM_STATES."
},
{
"name": "holderPresent",
"type": "boolean",
"required": true,
"description": "Indica si el holder sigue presente."
},
{
"name": "listing",
"type": "object",
"required": true,
"description": "Evidencia del Listing al que pertenece la asignación.",
"children": [
{
"name": "exists",
"type": "boolean",
"required": true,
"description": "Indica si el Listing persiste."
},
{
"name": "intent",
"type": "string",
"description": "Tipo de Listing observado. La rama erasing exige requirement."
},
{
"name": "deletionPending",
"type": "boolean",
"description": "Indica si el Requirement asociado ya está pendiente de eliminación."
},
{
"name": "referencesFile",
"type": "boolean",
"description": "Indica si el Listing aún referencia el archivo."
},
{
"name": "fileIdentityInScope",
"type": "boolean",
"description": "Indica si la identidad persistida del archivo está dentro del ámbito esperado."
}
]
},
{
"name": "references",
"type": "object",
"required": true,
"description": "Conteo de referencias al archivo.",
"children": [
{
"name": "count",
"type": "number",
"required": true,
"description": "Total de referencias observadas."
},
{
"name": "includesOwnListing",
"type": "boolean",
"required": true,
"description": "Indica si la referencia propia del Listing aparece en el conteo."
}
]
}
]
classifyConversationReference
[
{
"name": "target",
"type": "object",
"required": true,
"description": "Destino de la referencia activeRequirementId.",
"children": [
{
"name": "exists",
"type": "boolean",
"required": true,
"description": "Indica si el Requirement objetivo sigue existiendo."
},
{
"name": "intent",
"type": "string",
"description": "Tipo del objetivo observado."
},
{
"name": "sameOwner",
"type": "boolean",
"description": "Indica si el objetivo pertenece al mismo propietario."
},
{
"name": "deletionPending",
"type": "boolean",
"description": "Indica si el Requirement objetivo ya está pendiente de eliminación."
}
]
}
]
classifySharedFile
[
{
"name": "listingCount",
"type": "number",
"required": true,
"description": "Cantidad de Listings observados que referencian el archivo."
}
]
Behaviors
decision— Construye el objeto común de salida. CalcularequiresApprovala partir desuggestedActiony fijaauthorizesExecutionenfalse.missing— Detectanulloundefinedy centraliza la validación de campos ausentes.classifyPendingRequirement— Valida evidencia obligatoria, detecta cambios de estado durante el inventario, bloquea casos sin dueño, corta en cuentas no activas y distingue actividad directa de seguridad de reanudación.classifyMediaClaim— Valida una asignaciónMediaFileClaim, cruza dos lecturas de evidencia y separa estados coherentes, huérfanos, contradictorios y activos.classifyConversationReference— Clasifica referencias deConversationhacia un Requirement según existencia, tipo, propiedad compartida y estado de eliminación.classifySharedFile— Clasifica un archivo compartido observando solo el conteo disponible; no demuestra ausencia de otras referencias fuera del lote.
classifyPendingRequirement
Este clasificador trata los Requirements con deletion.status = "pending". Si la evidencia falta, devuelve UNRESOLVED con EVIDENCE_INCOMPLETE y sugiere rerun_inventory fileciteturn0file0turn0file3.
Las reglas visibles en el código son estas:
stillPending !== true→VERIFY_FIRSTconSTATE_CHANGED_DURING_INVENTORY.ownerExists !== true→MANUAL_ONLYconOWNER_MISSING.accountStatusausente →UNRESOLVED.accountStatus !== "active"→UNRESOLVEDconD1_OWNER_NOT_ACTIVE.fileClaimStatescon valores desconocidos →UNRESOLVEDconUNKNOWN_CLAIM_STATE.fileClaimStatesconerasing→VERIFY_FIRSTconMEDIA_ERASING_IN_PROGRESS.fileClaimStatescondeleting→VERIFY_FIRSTconMEDIA_DELETION_POSSIBLY_ACTIVE.reservations.live > 0oattachInProgress.live > 0→VERIFY_FIRSTconASSOCIATION_POSSIBLY_ACTIVE.- Sin señales de actividad →
AUTO_SAFEconRESUME_SAFE_EVEN_IF_ACTIVE.
Las pruebas confirman ese comportamiento. Verifican el caso AUTO_SAFE cuando no hay señales de actividad, VERIFY_FIRST cuando hay actividad posible, y UNRESOLVED ante evidencia incompleta fileciteturn0file6turn0file8turn0file11.
[!TIP] El comentario del archivo aclara que
AUTO_SAFEno significa abandono. Solo significa que reanudar sería seguro incluso si el proceso original siguiera vivo .
classifyMediaClaim
Este clasificador analiza una MediaFileClaim con evidencia cruzada entre la propia asignación y el Listing asociado. Si la evidencia falta, devuelve UNRESOLVED con EVIDENCE_INCOMPLETE fileciteturn0file0turn0file3.
Las ramas principales son estas:
- Estado desconocido →
UNRESOLVEDconUNKNOWN_CLAIM_STATE. listing.referencesFileyreferences.includesOwnListingno coinciden →VERIFY_FIRSTconCONTRADICTORY_EVIDENCE.deletingoerasingsin holder →MANUAL_ONLYconHOLDER_MISSING.activecon holder presente →MANUAL_ONLYconUNEXPECTED_HOLDER.deletedcon referencias ajenas →MANUAL_ONLYconDANGLING_FILE_REFERENCE.deletedsin la referencia propia →NO_ACTIONconTOMBSTONE.deletedcon referencia propia ylisting.deletionPending === true→NO_ACTIONconCOVERED_BY_REQUIREMENT_DELETION.deletedcon referencia propia pero sin pending →VERIFY_FIRSTconDELETED_FILE_REFERENCED_BY_OWN_LISTING.erasingcon referencias ajenas →MANUAL_ONLYconERASING_STILL_REFERENCED.erasingsinlisting.exists→MANUAL_ONLYconERASING_LISTING_GONE.erasingsinintent = requiremento sindeletionPending = true→MANUAL_ONLYcon variantes deERASING_OWN_LISTING_STILL_REFERENCESoERASING_REMOVAL_LEFTOVER.erasingsinfileIdentityInScope→MANUAL_ONLYconERASING_IDENTITY_EVIDENCE_MISSING.erasingcon todo en orden →VERIFY_FIRSTconERASING_DURING_REQUIREMENT_DELETION.deletingcondeletionPending === true→NO_ACTIONconCOVERED_BY_REQUIREMENT_DELETION.deletingsin pending →VERIFY_FIRSTconDELETION_IN_PROGRESS_OR_STALLEDoREMOVAL_LEFTOVER.activecon referencia al archivo →NO_ACTIONconATTACHED.activesin referencia →VERIFY_FIRSTconUNREFERENCED_ACTIVE_CLAIM.
Las pruebas cubren la prohibición total de AUTO_SAFE en erasing, la detección de evidencia contradictoria y el comportamiento por estado .
[!WARNING] El caso
erasingnunca se clasifica comoAUTO_SAFE. El clasificador siempre lo eleva a revisión o intervención humana fileciteturn0file0turn0file12.
classifyConversationReference
Este clasificador trata una Conversation con activeRequirementId. Si falta el destino o su bandera exists, devuelve UNRESOLVED con EVIDENCE_INCOMPLETE .
El comportamiento observado es este:
target.exists === false→MANUAL_ONLYconDANGLING_REQUIREMENT_REFERENCE.target.intentausente otarget.sameOwnerausente →UNRESOLVEDconEVIDENCE_INCOMPLETE.target.intent !== "requirement"→MANUAL_ONLYconREFERENCES_NON_REQUIREMENT.target.sameOwner === false→MANUAL_ONLYconCROSS_OWNER_REFERENCE.target.deletionPending === true→NO_ACTIONconCOVERED_BY_REQUIREMENT_DELETION.- En otro caso →
NO_ACTIONconVALID_REFERENCE.
Las pruebas confirman los casos colgantes, cruzados y válidos .
classifySharedFile
Este clasificador es el más simple. Solo necesita listingCount. Si falta, devuelve UNRESOLVED con EVIDENCE_INCOMPLETE. Si está presente, devuelve NO_ACTION con SHARED_FILE fileciteturn0file0turn0file3.
[!NOTE] El comentario del código es explícito: esta función solo informa. No prueba ausencia de otras referencias fuera del lote observado .
Workflows
1. Reunir evidencia | `deletion-inventory.service.js` obtiene datos de Requirements, claims, Conversations y archivos compartidos.
2. Validar campos obligatorios | Cada clasificador rechaza la evidencia incompleta con `UNRESOLVED`.
3. Detectar contradicciones | Las lecturas inconsistentes se elevan a `VERIFY_FIRST`.
4. Aplicar la política fija | El clasificador devuelve una decisión explicable con una acción sugerida.
5. Bloquear ejecución automática | `authorizesExecution` permanece en `false` y `requiresApproval` depende de `suggestedAction`.
Este flujo encaja con el inventario completo. El servicio de inventario agrupa secciones, aplica paginación y emite un resumen final, mientras este archivo solo resuelve la decisión por candidato .
Gotchas & edge cases
[!WARNING]
ageSecondsno decide nada por sí solo. El propio archivo aclara que la antigüedad es solo contexto y no una prueba de abandono fileciteturn0file0turn0file11.
[!CAUTION] Una evidencia incompleta nunca produce una clasificación segura. El resultado pasa a
UNRESOLVEDy sugiere repetir el inventario fileciteturn0file0turn0file3.
[!WARNING] Una evidencia contradictoria nunca se considera confiable. El resultado pasa a
VERIFY_FIRSTfileciteturn0file0turn0file12.
[!IMPORTANT] Ninguna clasificación de este módulo autoriza una escritura. La decisión puede sugerir pasos futuros, pero no ejecuta cambios .
Related
services/api/src/modules/reconciliation/deletion-inventory.service.js es el consumidor directo de estas funciones. Allí se reúne la evidencia de solo lectura, se paginan los candidatos y se emiten registros de inventario con estas clasificaciones .
services/api/src/modules/reconciliation/pending-requirement-recovery.service.js consume el contexto de recuperación, pero revalida las precondiciones justo antes de actuar. El propio archivo indica que una clasificación previa del inventario, incluso AUTO_SAFE, no basta por sí sola .
services/api/test/deletion-inventory-classify.test.js cubre los caminos deterministas del clasificador puro. services/api/test/e6-1-inventory-static.test.js y services/api/integration/mongo/e6-1-deletion-inventory.mongo.js confirman el carácter de solo lectura del inventario completo fileciteturn0file5turn0file11turn0file12turn0file4turn0file7.