games: gogobee learns to poll Pete for money it has to move

Pete holds the chips; we hold the euros and are the only one who can move
them. This is the loop that turns an escrow row on games.parodia.dev into a
real balance change here.

It is a poll because Pete cannot call us and isn't going to be able to. Every
step is keyed on the escrow guid, because every step can be interrupted in the
worst possible place: die after DebitIdem and before the verdict is queued,
and Pete re-offers the row, we claim it again, the debit replays as a no-op
and reports the same answer. The player is charged once. That is the only
property here that really matters, and there is a test that kills us three
times to prove it.

The verdict rides the queue that already carries adventure facts — it wants
the same durability, backoff and parking, so the row now says where it's going
rather than the queue growing a twin. Flush sends it at once instead of after
a 15s sender tick, because there's a person at the other end watching a
spinner.

First GET gogobee has ever made to Pete.
This commit is contained in:
prosolis
2026-07-13 23:00:11 -07:00
parent ab2bcf0c59
commit 6d402343e6
16 changed files with 5051 additions and 15 deletions

View File

@@ -457,6 +457,11 @@ func runMigrations(d *sql.DB) error {
// message arrives once; a poll loop whose ack is lost on the wire will
// retry, and without this the player pays twice. See euro.DebitIdem.
`ALTER TABLE euro_transactions ADD COLUMN external_id TEXT`,
// Pete games — the outbound queue carries more than adventure facts now.
// An escrow verdict goes to a different Pete endpoint, but it wants the
// same durability, backoff and parking, so it rides the same queue and the
// row says where it's going. Existing rows are all facts, hence the default.
`ALTER TABLE pete_emit_queue ADD COLUMN path TEXT NOT NULL DEFAULT '/api/ingest/adventure'`,
}
for _, stmt := range columnMigrations {
if _, err := d.Exec(stmt); err != nil {