services/api/src/modules/reconciliation/erasing-claim-reconciliation.service.js
Overview
Este módulo implementa la reconciliación interna de asignaciones MediaFileClaim que quedaron en estado "erasing". Su única transición permitida es erasing -> deleted, y solo cuando la evidencia del proveedor confirma NOT_FOUND_CONFIRMED mediante erasing-claim-evidence.js
El servicio trabaja sobre una ventana de consistencia controlada. Relee elegibilidad después de consultar al proveedor y luego aplica una escritura tipo compare-and-swap sobre MediaFileClaim. El propio archivo aclara que no borra archivos, no cambia el holder, no reanuda Requirement, y no decide por antigüedad
[!IMPORTANT] Este módulo es interno. El contexto válido solo se crea con
createReconciliationContext, y lleva unSymbolprivado del propio módulo
How it works
flowchart TD
Caller[Caller] --> Context[createReconciliationContext]
Caller --> Reconcile[reconcileErasingClaim]
Reconcile --> AssertContext[assertContext]
Reconcile --> ReadClaim[readClaimEvidence]
ReadClaim --> ClaimModel[MediaFileClaim]
ReadClaim --> ListingModel[Listing]
Reconcile --> Eligibility[erasingClaimEligibility]
Reconcile --> ReadReference[readReference]
ReadReference --> ClaimModel
ReadReference --> ListingModel
ReadReference --> Identity[persistedFileIdentity]
Reconcile --> ProbeLoop[observe x4]
ProbeLoop --> Probe[probe getFile]
Reconcile --> Classify[classifyProviderEvidence]
Classify --> ProviderDecision[provider evidence]
Reconcile --> ReRead[re-read eligibility]
ReRead --> Eligibility
Reconcile --> Cas[MediaFileClaim updateOne]
Cas --> Tombstone[set state deleted]
El flujo empieza validando el contexto interno y el contrato probe.getFile. Después lee la evidencia local, comprueba elegibilidad y obtiene una referencia válida si existe. A continuación consulta al proveedor cuatro veces, clasifica la evidencia y, solo si la respuesta es concluyente y sigue siendo elegible, ejecuta la lapida final con updateOne
Key components
{
"title": "erasing-claim-reconciliation.service.js",
"content": "Servicio interno que reconcilia asignaciones MediaFileClaim en estado erasing y solo permite la transici\u00f3n erasing a deleted cuando la evidencia externa confirma ausencia del archivo.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/erasing-claim-reconciliation.service.js",
"badges": [
"public"
]
}
{
"title": "createReconciliationContext",
"content": "Crea un contexto interno marcado con un Symbol privado. Valida operatorRef, reason y runId antes de congelar el objeto.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/erasing-claim-reconciliation.service.js",
"badges": [
"public"
]
}
{
"title": "RECONCILIATION_REASONS",
"content": "Lista congelada de motivos permitidos para crear el contexto de reconciliaci\u00f3n. En este archivo solo admite E63_ERASING_RECONCILIATION.",
"type": "enum",
"filePath": "services/api/src/modules/reconciliation/erasing-claim-reconciliation.service.js",
"badges": [
"public",
"read-only"
]
}
{
"title": "reconcileErasingClaim",
"content": "Reconciliaci\u00f3n principal. Lee evidencia local, consulta el proveedor, reeval\u00faa condiciones y aplica una escritura compare-and-swap sobre MediaFileClaim.",
"type": "function",
"filePath": "services/api/src/modules/reconciliation/erasing-claim-reconciliation.service.js",
"badges": [
"public"
]
}
Data & fields
Contexto de reconciliación
[
{
"name": "operatorRef",
"type": "string",
"required": true,
"description": "Referencia del operador que ejecuta la reconciliación. Debe coincidir con el patrón SAFE_REF."
},
{
"name": "reason",
"type": "string",
"required": true,
"description": "Motivo de reconciliación. Debe estar incluido en RECONCILIATION_REASONS."
},
{
"name": "runId",
"type": "string",
"required": true,
"description": "Identificador de ejecución. Debe coincidir con el patrón SAFE_REF."
}
]
El contexto resultante contiene un Symbol privado llamado CONTEXT_BRAND, más operatorRef, reason y runId. El archivo lo congela con Object.freeze y lo usa como marca interna de integridad
Entrada de reconcileErasingClaim
[
{
"name": "fileId",
"type": "string",
"required": true,
"description": "Identificador del archivo cuya asignación en erasing se quiere reconciliar."
},
{
"name": "expected",
"type": "object",
"required": true,
"description": "Estado esperado para la compare-and-swap.",
"children": [
{
"name": "listingId",
"type": "string",
"required": true,
"description": "Id del Listing asociado en la comprobación."
},
{
"name": "holder",
"type": "string",
"required": true,
"description": "Id del holder esperado en la comprobación."
},
{
"name": "updatedAt",
"type": "Date | string",
"required": true,
"description": "Marca temporal esperada para detectar cambios intermedios."
}
]
},
{
"name": "referenceFileId",
"type": "string",
"required": true,
"description": "Archivo de referencia activo usado para validar identidad persistida."
},
{
"name": "context",
"type": "object",
"required": true,
"description": "Contexto interno creado por createReconciliationContext."
},
{
"name": "probe",
"type": "object",
"required": true,
"description": "Adaptador que expone getFile(fileId)."
},
{
"name": "hooks",
"type": "object",
"description": "Gancho opcional solo para pruebas. Permite ejecutar callbacks en puntos concretos del flujo."
}
]
Lectura local de evidencia
El módulo usa MediaFileClaim.findById(fileId).lean() para leer la asignación actual. Si existe, también carga el Listing relacionado con un conjunto concreto de campos: _id, intent, createdBy, deletion.status, media.fileId, media.provider, media.filePath y sourceDocuments.fileId
Además, cuenta referencias externas en otros Listing mediante estos campos:
media.fileIdsourceDocuments.fileId
Eso alimenta foreignReferences, que forma parte de la evaluación de elegibilidad
Evidencia del archivo de referencia
readReference(referenceFileId) solo acepta una asignación activa. Busca la MediaFileClaim, exige state === "active", carga su Listing y comprueba la identidad persistida con persistedFileIdentity
Si la identidad no está presente, devuelve null. Si existe, devuelve:
fileIdfilePath
Observaciones del proveedor
observe(probe, subject, fileId) llama probe.getFile(fileId) y envuelve el resultado con subject. Si el proveedor lanza una excepción, el módulo la transforma en una observación con:
kind: "error"status: nullerrorClass: "PROBE_THREW"
Behaviors
createReconciliationContext— validaoperatorRef,reasonyrunId, y devuelve un contexto congelado con una marca privada.assertContext— verifica que el contexto lleve la marca interna del módulo.step— ejecuta hooks de prueba si existen.errorCode— normaliza el código de error a una cadena truncada de 64 caracteres.readClaimEvidence— lee laMediaFileClaim, suListingasociado y el número de referencias externas.readReference— valida una referencia activa y extrae su identidad persistida.observe— consultaprobe.getFiley convierte fallos en observaciones de error.reconcileErasingClaim— coordina toda la reconciliación y devuelve un objeto conoutcome,reasony, a veces,code
Outcome contract
reconcileErasingClaim puede devolver estos resultados:
tombstoned— la compare-and-swap modificó exactamente un documento.cas_lost— la escritura no coincidió con el estado esperado.exists— el proveedor confirma que el archivo sigue existiendo.unknown— la evidencia del proveedor no es concluyente o es contradictoria.manual_only— la evidencia local impide automatizar la reconciliación.changed— las precondiciones cambiaron entre la primera lectura y la segunda.not_erasing— la asignación ya no está en estado de erasing.not_found— no existe la asignación.pending— ocurrió un fallo en la escritura de la lapida y el módulo lo convierte en resultado controlado
Internal validation and guards
createReconciliationContext
La función rechaza valores fuera del patrón SAFE_REF, que admite caracteres alfanuméricos, punto, guion bajo y guion medio, con longitud de 1 a 64. También exige que reason pertenezca a RECONCILIATION_REASONS
assertContext
El módulo usa assertContext para impedir el uso accidental del servicio sin contexto válido. Si falta la marca interna, lanza:
reconciliation: contexto interno requerido (createReconciliationContext)
probe.getFile
Si probe falta o no expone getFile, el servicio lanza un error inmediato. El contrato mínimo es estricto y visible en el código
[!NOTE] El archivo solo usa el proveedor para leer con
getFile. No contiene operaciones de borrado sobre el proveedor externo
Reconciliation flow
- Valida el contexto interno.
- Verifica que
probe.getFileexista. - Lee la evidencia local con
readClaimEvidence. - Evalúa elegibilidad con
erasingClaimEligibility. - Si la elegibilidad falla, devuelve ese resultado sin continuar.
- Busca una referencia válida con
readReference. - Si la referencia no existe o no es válida, devuelve
unknown. - Ejecuta
probe.getFilecuatro veces en secuencia fija. - Clasifica la evidencia con
classifyProviderEvidence. - Si el proveedor confirma existencia, devuelve
exists. - Si la evidencia no confirma ausencia, devuelve
unknown. - Relee la evidencia local.
- Si algo cambió, devuelve
changed. - Aplica
MediaFileClaim.updateOnecon la condición exacta observada. - Si la escritura modifica un documento, devuelve
tombstoned. - Si no modifica ninguno, devuelve
cas_lost
1. Validate context | The service requires a branded context from createReconciliationContext.
2. Read local evidence | It reads the claim, its listing, and foreign references.
3. Confirm eligibility | erasingClaimEligibility must return eligible before any provider call.
4. Read reference | The reference file must belong to an active claim with persisted identity.
5. Probe the provider | The module performs four ordered getFile reads.
6. Classify evidence | classifyProviderEvidence decides whether the target exists or is confirmed absent.
7. Recheck conditions | The service rereads local state after the external call.
8. Tombstone the claim | updateOne converts erasing to deleted only if the compare-and-swap still matches.
Gotchas & edge cases
[!WARNING] La reconciliación puede perder validez entre la primera lectura y la escritura final. En ese caso devuelve
changedocas_lost
[!WARNING] Si la evidencia del proveedor no confirma ausencia de forma consistente, el resultado es
unknown. El módulo no fuerza la lapida en ese caso fileciteturn0file18turn0file1
[!WARNING] El archivo no borra archivos del proveedor externo. Tampoco reanuda
Requirementni cambia el holder de la asignación
[!CAUTION] La compare-and-swap incluye
_id,listingId,state,holderyupdatedAt. Cualquier cambio intermedio invalida la operación final
[!NOTE]
hookssolo existe para pruebas. Los nombres usados por el código sonreconcile:beforeProbe,reconcile:afterProbeyreconcile:beforeCas
Related
Este módulo depende directamente de services/api/src/modules/reconciliation/erasing-claim-evidence.js para dos decisiones clave: la elegibilidad de la asignación y la clasificación de la evidencia del proveedor fileciteturn0file1turn0file17
También se apoya en los modelos Listing y MediaFileClaim. El primero aporta la vista de identidad persistida y referencias cruzadas; el segundo es la unidad principal de reconciliación y la única escritura final del módulo
La semántica observada en las pruebas del repositorio confirma que este servicio se usa para cerrar casos de erasing bloqueados por evidencia externa, sin introducir borrados adicionales ni flujos de recuperación de Requirement dentro de este archivo fileciteturn0file9turn0file11