package calls import ( "context" "amadan.net/oficina/calls/internal/calls/hub" ) // hubStanding is hub.Config.StandingFor: the app's standing (spec §2.3) // in the two facts the hub keeps — the kind a participation holds, and // whether the person still has entry authority for the room at all. // // It is never called under the hub's lock: the hub drops the lock // before every standing read, because this reads the database. // // A meeting that has ended, or that no longer exists, answers ok=false // with no error, which is what a lapsed grant answers too: the tick // then applies §2.4's transitions rather than treating it as a failure // to retry on the next tick. // // That ended-meeting arm is belt and braces, and deliberately so // (review W-5): every path that reaches a room already refuses an ended // meeting earlier — roomAccess on the pages, and the stream handler // which reads ended_at before it attaches, because End no longer writes // a headstone for a room that was never opened. Removing it here would // break nothing today and would make this function's answer depend on // its callers all continuing to check first. func (a *app) hubStanding(ctx context.Context, sqid string, personID int64) (hub.Kind, bool, error) { m, ok, err := a.store.meetingBySqid(ctx, sqid) if err != nil { return hub.KindGuest, false, err } if !ok || m.EndedAt != nil { return hub.KindGuest, false, nil } p, ok, err := a.store.personByID(ctx, personID) if err != nil { return hub.KindGuest, false, err } if !ok { return hub.KindGuest, false, nil } st, err := a.standingFor(ctx, p, m) if err != nil { return hub.KindGuest, false, err } switch st { case StandingMember: return hub.KindMember, true, nil case StandingGuest: return hub.KindGuest, true, nil } return hub.KindGuest, false, nil }