mobieusKnow Handling coach access and consent tickets History #508
Author
Patrick Bass
Submitted
Aug 24, 2026 12:01am
Summary
Organizations: join codes, what a gym can and cannot see, and what to escalate
Coach access tickets are almost always about consent, and the answer is almost
always that the member controls it. Never adjust a grant on a member's behalf.
== The one rule ==
'''Being on a coach's roster grants nothing.''' The roster is a referral list.
Everything a coach can see is a separate, per-category permission the member turned
on themselves, and permission is re-checked on every single read - so revoking
closes the history too, not just what happens next.
== The ten categories ==
A member shares, one at a time: weight; activity; workouts; body measurements;
progress photos; nutrition; fasting; cycle; supplements; streaks.
'''Workouts, measurements and photos are three separate choices.''' Sharing a
training log does not share measurements, and neither shares photos. If a member
says "my coach can see my photos and I never allowed that", check before believing
it is a bug - but do check, because it would be serious:
<syntaxhighlight lang="sql">
SELECT field, granted_at, revoked_at
FROM loopa_coach_share_grants
WHERE user_id = ? AND coach_id = ?;
</syntaxhighlight>
A row with <code>revoked_at IS NULL</code> is live. No row at all means denied - the
absence is the denial, and there is no default-allow anywhere in the path.
== "My coach says they cannot see my workouts" ==
Walk the same order the code does:
+ # '''Is the coach active?''' A terminated coach fails before any per-field lookup.
+ # '''Is either party blocked?''' Blocking severs access in both directions without
1. '''Is the coach active?''' A terminated coach fails before any per-field lookup.
1. '''Is either party blocked?''' Blocking severs access in both directions without
touching the grants, so the grants can look fine and the reads still fail.
+ # '''Is the roster link live?''' <code>loopa_coach_clients.ended_at</code> must be NULL.
+ # '''Is there a live <code>workouts</code> grant?''' This is the usual answer.
1. '''Is the roster link live?''' <code>loopa_coach_clients.ended_at</code> must be NULL.
1. '''Is there a live <code>workouts</code> grant?''' This is the usual answer.
Send the member to '''More, then Preferences, then Your coach'''. They turn it on.
You do not.
== "Can my coach schedule training for me?" ==
Only if the member has shared workouts. Assigning a program is a write and takes a
stricter test than reading: a coach who can read a client's training is not thereby
entitled to put a program on their calendar. Without the grant the coach gets a
plain refusal saying the client has not shared workouts with them.
== "Can I see when my coach looked?" ==
Yes, and point them at it - a control the member can inspect is worth more than one
they are told about. It is under '''More, then Preferences, then Your Data''', and it
distinguishes a coach glancing at their client list from a coach opening that member
and reading.
That distinction is deliberate. Before it existed, a coach opening a list of forty
names wrote the same record as a coach reading one person, and the member's screen
overstated what had happened. A log that cries wolf gets ignored.
== "My coach messaged me / can I message my coach?" ==
There is '''no coach-to-client messaging in Loopa''', and that is a design decision
rather than a missing feature. Do not promise it is coming.
What exists is bounded and asymmetric:
* A coach can comment on a '''completed session''' the member has shared - at most ten
per client per day.
* The member can '''reply on that thread'''. They cannot start one. A member with no
coach comment on a session has nothing to reply to, and the app says so.
* Recommendations: three per client per day, one way, no reply path.
* A plan offer, whose only member-to-coach text is a decline reason.
* Two forum broadcasts a day into the coach's private forum.
A coach asking support to relay a longer message to a client should be told no. The
bounds are the product.
== Reporting a coach comment ==
Members can report any comment on their own session. A reported comment keeps
<code>reported_at</code> and a reason; a moderated one is '''hidden''' and is never
+ served again - not greyed out with a "removed" placeholder, which would reproduce
served again - not grayed out with a "removed" placeholder, which would reproduce
the harm.
Coach suspension is <code>loopa_coaches.status</code> and only Patrick sets it. If a
report needs that, escalate; do not action it yourself.
== What to escalate immediately ==
A member reporting that a coach saw a category they never enabled, where the grant
table shows no live row for it. That would be a boundary failure rather than a
support question, and it goes straight to engineering with the user id, the coach id
and the timestamp.

Coach access tickets are almost always about consent, and the answer is almost always that the member controls it. Never adjust a grant on a member's behalf.

== The one rule ==

'''Being on a coach's roster grants nothing.''' The roster is a referral list. Everything a coach can see is a separate, per-category permission the member turned on themselves, and permission is re-checked on every single read - so revoking closes the history too, not just what happens next.

== The ten categories ==

A member shares, one at a time: weight; activity; workouts; body measurements; progress photos; nutrition; fasting; cycle; supplements; streaks.

'''Workouts, measurements and photos are three separate choices.''' Sharing a training log does not share measurements, and neither shares photos. If a member says "my coach can see my photos and I never allowed that", check before believing it is a bug - but do check, because it would be serious:

SELECT field, granted_at, revoked_at FROM loopa_coach_share_grants WHERE user_id = ? AND coach_id = ?;

A row with revoked_at IS NULL is live. No row at all means denied - the absence is the denial, and there is no default-allow anywhere in the path.

== "My coach says they cannot see my workouts" ==

Walk the same order the code does:

# '''Is the coach active?''' A terminated coach fails before any per-field lookup. # '''Is either party blocked?''' Blocking severs access in both directions without touching the grants, so the grants can look fine and the reads still fail. # '''Is the roster link live?''' loopa_coach_clients.ended_at must be NULL. # '''Is there a live workouts grant?''' This is the usual answer.

Send the member to '''More, then Preferences, then Your coach'''. They turn it on. You do not.

== "Can my coach schedule training for me?" ==

Only if the member has shared workouts. Assigning a program is a write and takes a stricter test than reading: a coach who can read a client's training is not thereby entitled to put a program on their calendar. Without the grant the coach gets a plain refusal saying the client has not shared workouts with them.

== "Can I see when my coach looked?" ==

Yes, and point them at it - a control the member can inspect is worth more than one they are told about. It is under '''More, then Preferences, then Your Data''', and it distinguishes a coach glancing at their client list from a coach opening that member and reading.

That distinction is deliberate. Before it existed, a coach opening a list of forty names wrote the same record as a coach reading one person, and the member's screen overstated what had happened. A log that cries wolf gets ignored.

== "My coach messaged me / can I message my coach?" ==

There is '''no coach-to-client messaging in Loopa''', and that is a design decision rather than a missing feature. Do not promise it is coming.

What exists is bounded and asymmetric:

* A coach can comment on a '''completed session''' the member has shared - at most ten per client per day. * The member can '''reply on that thread'''. They cannot start one. A member with no coach comment on a session has nothing to reply to, and the app says so. * Recommendations: three per client per day, one way, no reply path. * A plan offer, whose only member-to-coach text is a decline reason. * Two forum broadcasts a day into the coach's private forum.

A coach asking support to relay a longer message to a client should be told no. The bounds are the product.

== Reporting a coach comment ==

Members can report any comment on their own session. A reported comment keeps reported_at and a reason; a moderated one is '''hidden''' and is never served again - not greyed out with a "removed" placeholder, which would reproduce the harm.

Coach suspension is loopa_coaches.status and only Patrick sets it. If a report needs that, escalate; do not action it yourself.

== What to escalate immediately ==

A member reporting that a coach saw a category they never enabled, where the grant table shows no live row for it. That would be a boundary failure rather than a support question, and it goes straight to engineering with the user id, the coach id and the timestamp.

Coach access tickets are almost always about consent, and the answer is almost
always that the member controls it. Never adjust a grant on a member's behalf.

== The one rule ==

'''Being on a coach's roster grants nothing.''' The roster is a referral list.
Everything a coach can see is a separate, per-category permission the member turned
on themselves, and permission is re-checked on every single read - so revoking
closes the history too, not just what happens next.

== The ten categories ==

A member shares, one at a time: weight; activity; workouts; body measurements;
progress photos; nutrition; fasting; cycle; supplements; streaks.

'''Workouts, measurements and photos are three separate choices.''' Sharing a
training log does not share measurements, and neither shares photos. If a member
says "my coach can see my photos and I never allowed that", check before believing
it is a bug - but do check, because it would be serious:

<syntaxhighlight lang="sql">
SELECT field, granted_at, revoked_at
  FROM loopa_coach_share_grants
 WHERE user_id = ? AND coach_id = ?;
</syntaxhighlight>

A row with <code>revoked_at IS NULL</code> is live. No row at all means denied - the
absence is the denial, and there is no default-allow anywhere in the path.

== "My coach says they cannot see my workouts" ==

Walk the same order the code does:

# '''Is the coach active?''' A terminated coach fails before any per-field lookup.
# '''Is either party blocked?''' Blocking severs access in both directions without
   touching the grants, so the grants can look fine and the reads still fail.
# '''Is the roster link live?''' <code>loopa_coach_clients.ended_at</code> must be NULL.
# '''Is there a live <code>workouts</code> grant?''' This is the usual answer.

Send the member to '''More, then Preferences, then Your coach'''. They turn it on.
You do not.

== "Can my coach schedule training for me?" ==

Only if the member has shared workouts. Assigning a program is a write and takes a
stricter test than reading: a coach who can read a client's training is not thereby
entitled to put a program on their calendar. Without the grant the coach gets a
plain refusal saying the client has not shared workouts with them.

== "Can I see when my coach looked?" ==

Yes, and point them at it - a control the member can inspect is worth more than one
they are told about. It is under '''More, then Preferences, then Your Data''', and it
distinguishes a coach glancing at their client list from a coach opening that member
and reading.

That distinction is deliberate. Before it existed, a coach opening a list of forty
names wrote the same record as a coach reading one person, and the member's screen
overstated what had happened. A log that cries wolf gets ignored.

== "My coach messaged me / can I message my coach?" ==

There is '''no coach-to-client messaging in Loopa''', and that is a design decision
rather than a missing feature. Do not promise it is coming.

What exists is bounded and asymmetric:

* A coach can comment on a '''completed session''' the member has shared - at most ten
  per client per day.
* The member can '''reply on that thread'''. They cannot start one. A member with no
  coach comment on a session has nothing to reply to, and the app says so.
* Recommendations: three per client per day, one way, no reply path.
* A plan offer, whose only member-to-coach text is a decline reason.
* Two forum broadcasts a day into the coach's private forum.

A coach asking support to relay a longer message to a client should be told no. The
bounds are the product.

== Reporting a coach comment ==

Members can report any comment on their own session. A reported comment keeps
<code>reported_at</code> and a reason; a moderated one is '''hidden''' and is never
served again - not greyed out with a "removed" placeholder, which would reproduce
the harm.

Coach suspension is <code>loopa_coaches.status</code> and only Patrick sets it. If a
report needs that, escalate; do not action it yourself.

== What to escalate immediately ==

A member reporting that a coach saw a category they never enabled, where the grant
table shows no live row for it. That would be a boundary failure rather than a
support question, and it goes straight to engineering with the user id, the coach id
and the timestamp.