Skip to content

fix(resolve): correct persistence and cooldown state transitions - #23

Open
LordMikkel wants to merge 2 commits into
vmlcode:developfrom
LordMikkel:fix/resolve-flow
Open

fix(resolve): correct persistence and cooldown state transitions#23
LordMikkel wants to merge 2 commits into
vmlcode:developfrom
LordMikkel:fix/resolve-flow

Conversation

@LordMikkel

@LordMikkel LordMikkel commented Jul 1, 2026

Copy link
Copy Markdown

¿Qué hace este PR?

Corrige cómo el backend guarda y transiciona el flujo resolve (SOS resuelto por testigos).

¿Por qué?

Si un rescatista llega al sitio de un SOS, toma foto como evidencia, y firma que el SOS fue atendido. El backend debería:

  1. Guardar al testigo.
  2. Esperar a que lleguen más testigos hasta completar el quorum (por defecto 2).
  3. Arrancar un cooldown de 15 minutos.
  4. Si nadie lo contradice, marcar como cleared.
  5. Si el originador vuelve a emitir SOS, marcar como disputed.

Ahora esto falla de dos formas:

Con Supabase: POST /resolve devuelve 500. La tabla resolve_witnesses tiene una FK que exige que ya exista el receipt padre en resolve_receipts. Pero el código intenta guardar testigos antes de crear ese receipt. No puedes crear el receipt sin los testigos, ni guardar los testigos sin el receipt.

Sin Supabase (modo desarrollo): el backend acepta el resolve, pero nunca registra el cooldown en memoria. Hay un mapa pendingClears que debería llenarse cuando un receipt entra en cooldown, pero nadie lo llama. Así que al pasar los 15 minutos, el sweeper no tiene nada que promover. El SOS se queda como pending para siempre.

¿Cómo lo arregla?

  • resolve_witnesses ya no tiene receipt_id. Se relaciona por target_sos_id, que es la clave estable del flujo.
  • resolve_receipts ahora garantiza un único receipt por target_sos_id (que es lo que la documentación ya dice).
  • upsertReceipt(...) guarda por target_sos_id en vez de por id (que cambia con cada testigo nuevo).
  • El store en memoria registra el cooldown automáticamente cuando un receipt tiene cooldownEndsAt.
  • Las queries de veto ahora solo miran receipts que ya están en cooldown real (cooldown_ends_at IS NOT NULL).

Test manual

Probado localmente sin Supabase:

  1. Ingesta un SOS de device por /ingest → OK
  2. Envío witness 1 por /resolveaccepted: true, quorumSeen: 1, quorum no alcanzado
  3. Envío witness 2 por /resolveaccepted: true, quorumSeen: 2, quorum alcanzado, cooldownEndsAt seteado
  4. Canal resuelto emitió registro firmado con event: "pending" → OK
  5. Envío witness 3 → aceptado, cooldownEndsAt se mantiene (no se reinicia)

¿Cómo probarlo?

  • bun run --cwd backend tsc --noEmit pasa limpio
  • bun test pasa (31 tests)
  • Test manual: POST /resolve con quorum funciona sin Supabase
  • Probar con Supabase configurado (ya no debería dar 500)

Checklist

  • Los tests existentes siguen pasando (bun test)
  • No toca wire format ni protocolo de malla
  • No toca /ingest ni firma Ed25519

@davgonsan davgonsan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and ran the suite — the change looks sound. Dropping the receipt_id FK from resolve_witnesses and keying on target_sos_id (with resolve_receipts.target_sos_id now unique) is consistent across resolvesRepo, routes.ts, store.ts, and both schema.sql + the new migration. I confirmed no dangling receipt_id references remain (the leftover receiptId in routes/store is the receipt's own PK / the in-memory pendingClears value, which is correct), and the in-memory upsertReceipt now keeps pendingClears in sync — good parity with the Supabase path.

Verified: bun test → 31 pass / 0 fail.

Two things before merge:

  1. No test covers the changed flow. The resolve tests live in packages/shared/src/resolve.test.ts and only exercise the canonical/crypto format — there's no backend store/routes test. So the "corrected persistence and cooldown state transitions" this PR fixes aren't proven by anything. For a persistence + state-machine fix I'd really want a store-level test (pending → cooldown → cleared, and the one-pending-per-author invariant via pendingClears). Happy to help add it.

  2. Migration could fail on existing data. 0001_fix_resolve_schema.sql runs add constraint … unique (target_sos_id) without deduping first — if any duplicate target_sos_id rows already exist in resolve_receipts, the ALTER fails and the whole begin/commit rolls back. Low risk pre-prod, but a dedupe step (or a comment noting the assumption) would make it safe to run anywhere.

Also: please retarget to develop rather than main, to match where the rest of the backend work is landing.

@LordMikkel
LordMikkel changed the base branch from main to develop July 2, 2026 01:05
@LordMikkel

LordMikkel commented Jul 2, 2026

Copy link
Copy Markdown
Author

Thanks, addressed all three points:

  • Added 6 tests for the resolve store: cooldown registration, promotion to cleared, originator veto, and dedup
  • Migration now cleans up duplicate rows before adding the unique constraint
  • Retargeted to develop and rebased

@LordMikkel

Copy link
Copy Markdown
Author

@vmlcode Me gustaría poder seguir aportando al proyecto pero veo que estos últimos días ha parado mucho la actividad y hasta tener estas dos primeras PR no he querido seguir desarrollando, es posible estar en algún grupo o chat de trabajo con ustedes para estar al tanto de como va avanzando el proyecto?

@vmlcode

vmlcode commented Jul 8, 2026

Copy link
Copy Markdown
Owner

@vmlcode Me gustaría poder seguir aportando al proyecto pero veo que estos últimos días ha parado mucho la actividad y hasta tener estas dos primeras PR no he querido seguir desarrollando, es posible estar en algún grupo o chat de trabajo con ustedes para estar al tanto de como va avanzando el proyecto?

Buenas @LordMikkel. Un placer, si la actividad ha bajado un poco últimamente debido a que estamos conversando con algunos miembros externos interesados en el app, coordinando casos de usos así como el futuro de la aplicación en general (El como expandirnos), me encantaría que fueras parte de la discusión entonces te invito al discord de guacamalla para que coordinemos en conjunto la siguiente fase de este proyecto

https://discord.gg/ycEnCFTZx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants