mischief: gogobee learns to fill orders placed on Pete's web board

The game-side half of Mischief Makers M3. gogobee polls Pete for storefront
orders and opens the contract itself — the money, the eligibility, the fight
are all its own, exactly as a Matrix !mischief buy.

- roster push now carries each buyer's advisory euro balance (keyed by localpart,
  a separate keyspace from the anonymous board token) and the live tier catalog,
  so the storefront renders gogobee's current prices and never hardcodes one
- placeWebMischief: the fulfilment path, debit-first-then-refund so the money
  state is a pure function of the order guid. DebitIdem + an order_guid stamp on
  the contract make a retried claim neither double-charge nor double-open
- resolveRosterToken recomputes the one-way board token per live player to name
  a mark; the buyer is @<username>:<homeserver> from Authentik's preferred_username
- a 30s poll loop drives it; a lost verdict just leaves the order pending to
  re-run, so the loop is its own retry and needs no durable queue
- euro.HasExternalTx lets the affordability gate tell a first attempt (no debt
  for a mischief buy, like Matrix) from a retry of one already paid

order_guid column added to mischief_contracts (schema + idempotent ALTER).
This commit is contained in:
prosolis
2026-07-14 21:12:37 -07:00
parent 6d402343e6
commit ba306f0b59
9 changed files with 646 additions and 12 deletions

View File

@@ -49,7 +49,7 @@ func TestRosterSnapshotReadsTheClock(t *testing.T) {
// Never acted at all: the fold falls through to created_at.
seedRosterPlayer(t, "@never:test", "Camcast", &old, nil)
snap, err := buildRosterSnapshot(now)
snap, err := buildRosterSnapshot(now, nil)
if err != nil {
t.Fatalf("buildRosterSnapshot: %v", err)
}
@@ -94,7 +94,7 @@ func TestRosterOmitsOptedOut(t *testing.T) {
seedRosterPlayer(t, "@hidden:test", "Quack", &old, &old)
setNewsOptout("@hidden:test", true)
snap, err := buildRosterSnapshot(now)
snap, err := buildRosterSnapshot(now, nil)
if err != nil {
t.Fatalf("buildRosterSnapshot: %v", err)
}