Ola 0 · Sebas 0.1: conectar el guard de aceptación única - #13
Open
heysebitas wants to merge 2 commits into
Open
heysebitas wants to merge 2 commits into
heysebitas wants to merge 2 commits into
Conversation
`RoutingStore.respond()` ya tenia idempotencia por requestKey, verificaba
`accepted_destination` y bajo Postgres usa pg_advisory_xact_lock + select
for update. Estaba bien hecho y no lo llamaba nadie: la ruta que acepta de
verdad es POST /handshake/respond, que solo miraba el estado de ESE
handshake y nunca preguntaba si otra sede ya acepto el caso.
Lo que tapaba el hueco era que el fan-out es secuencial. El dia que alguien
active fan-out paralelo —la optimizacion obvia— dos hospitales preparan
cama para el mismo paciente.
- HandshakeService inyecta RoutingService y reserva el destino ANTES de
escribir la aceptacion. requestKey = handshakeId + decision, para que el
doble toque siga siendo idempotente.
- Si el guard rechaza: aplicada:false + codigo, el handshake NO se toca
(sigue 'enviado' y vence solo) y la sede perdedora no suma una
aceptacion que nunca ocurrio a su P(aceptacion).
- RespondResponse.codigo (opcional, espejado): el caso que importa no se
ve en handshake.estado. Telegram y /hospital ahora dicen "otra sede ya
acepto este caso · no prepare cama" en vez de "ya estaba enviado".
- RoutingService.aceptarDestino(): el guard sin la precondicion de
evidencia, que pertenece a POST /dispatch. La sede que acepta no siempre
es la #1 del ranking y el estado de ruteo vive en RAM hasta la 1.2;
encadenar la reserva a esa precondicion dejaria la carrera abierta justo
cuando hay varias sedes tocadas. `respond()` sigue exigiendola y ahora
delega aqui: un solo camino hacia el store.
- RoutingResponseCommand.evidence pasa a opcional. Sin evidencia se
reserva igual pero no se escribe fila de auditoria: un renglon inventado
es peor que un hueco, y el hueco queda en el log.
No se duplico ni una linea del guard. Verificado que los tests nuevos fallan
al desconectarlo (4 de 14) y pasan al conectarlo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cia entre procesos Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tarea 0.1 · va sobre #12, mergear ese primero.
El problema
RoutingStore.respond()ya tenía idempotencia porrequestKey, verificabaaccepted_destinationy bajo Postgres usapg_advisory_xact_lock+select … for update. Estaba bien hecho y no lo llamaba nadie.La ruta que acepta de verdad es
POST /handshake/respond, que solo miraba el estado de ese handshake y nunca preguntaba si otra sede ya aceptó el caso. Lo que tapaba el hueco era que el fan-out es secuencial. El día que alguien active fan-out paralelo —la optimización obvia— dos hospitales preparan cama para el mismo paciente.Qué trae
HandshakeServiceinyectaRoutingServicey reserva el destino antes de escribir la aceptación.requestKey = handshakeId + decisión, para que el doble toque siga siendo idempotente.aplicada: false+codigo, el handshake no se toca (sigueenviadoy vence solo) y la sede perdedora no suma una aceptación que nunca ocurrió a su P(aceptación).RespondResponse.codigo(opcional, espejado): el caso que importa no se ve enhandshake.estado. Telegram y/hospitalahora dicen "otra sede ya aceptó este caso · no prepare cama" en vez de "ya estaba enviado".Dos decisiones que quiero que miren
RoutingService.respond()directo. Exige evidencia de despacho para ese destino, y la sede que acepta casi nunca es la Init sdd config #1 del ranking (el fan-out toca varias, el vigilante re-rutea). Encadenar la reserva a esa precondición dejaba la carrera abierta justo cuando hay varias sedes tocadas. AgreguéaceptarDestino(): mismostore.respond—el guard es uno solo, no dupliqué nada—, adjunta evidencia si existe.respond()sigue exigiéndola y ahora delega ahí.RoutingResponseCommand.evidencepasó a opcional. Sin evidencia se reserva igual pero no se escribe fila de auditoría: los checks depulso_routing_decision_auditexigenmodelVersion/configVersiony rellenarlos sería falsificar el acta. El hueco queda declarado en el log.Hecho cuando
aplicada: falseVerificado que el test detecta la ausencia del guard: desconectándolo fallan 4 de 14 tests de handshake; conectado pasan los 14. El de concurrencia pone dos
procesarRespuestaen vuelo a la vez con frontera asíncrona en el store, así que ambas pasan el chequeo de estado antes de que ninguna reserve. La concurrencia entre procesos ya la cubrepostgres-routing.store.spec.ts, que necesitaPULSO_TEST_DATABASE_URL— se enciende en 1.7.🤖 Generated with Claude Code