You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 00a8e83
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: features/orgs/phase3.md
+31-21Lines changed: 31 additions & 21 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -123,15 +123,18 @@ the removal UI of phase 4 reachable, and 2.1 is what makes it safe.
123
123
-`transfer probes`, `transfer tokens`, `transfer credits` - one per entity kind, each its own transaction. A user may hand over
124
124
their probes and keep their credits; nothing forces the three to happen together or in any order.
125
125
-`credits-redirect` GET - the redirects where the active account's github id is the source or the target. It is the only
126
-
reader of the table (1.4), so it is also what keeps a viewer from seeing them. Each side comes back with its name - the user's
126
+
reader of the table (1.4), and the org half of it is admin-only for the reason 1.4 gives: the counterparty of an org's redirect
127
+
is a person no other row a member can read names. Each side comes back with its name - the user's
127
128
`github_username` or the org's `name`, the bare id when neither table knows it - because the dash shows `john => acme-org` and
128
129
can read no other user's row.
129
-
-`credits-redirect` POST - manages the redirect whose **source is the active account's github id**, which is the one that gives
130
-
its money away. Acting personally, a user points their sponsorship at an org they belong to, replaces that choice, or clears it
131
-
with `orgId: null`. Acting as an org, an admin can only clear: the `org -> user` rows we inherited are removable by the org that
132
-
funds them, which is the one thing that lets a redirect be undone when its target is gone. Creation stays one-directional -
133
-
user -> org, towards an org the caller belongs to. Pointing at an org that is itself redirected is allowed and needs no thought:
134
-
the lookup is one hop (1.3).
130
+
-`credits-redirect` POST - a person points their sponsorship at an org they belong to. Only that direction exists: an org
131
+
receives a sponsorship, it never passes one on. It also ends every row that touches the caller, in either direction - sending
132
+
their own money somewhere says they are nobody's stand-in any more. Pointing at an org that is itself redirected is allowed and
133
+
needs no thought: the lookup is one hop (1.3).
134
+
-`credits-redirect` DELETE - removes one named row, `{ source, target }`, and either of the two sides may do it: the one giving
135
+
its sponsorship away, and the one that has been receiving it. Naming the row is what lets an org end a single member's donation
136
+
rather than all of them, and it is what makes the inherited `org -> user` rows removable - by the org that funds them, and by
137
+
the person they route through.
135
138
136
139
The org is named by id; the caller is the session user, acting as the active account.
137
140
@@ -143,11 +146,11 @@ The org is named by id; the caller is the session user, acting as the active acc
143
146
| transfer tokens | always | always | never |
144
147
| transfer credits | always | always | never |
145
148
|`credits-redirect` POST, own sponsorship | always | always | never |
146
-
|`credits-redirect`POST, clearing the org's own redirect | always | never | never |
149
+
|`credits-redirect`DELETE, a row this account is a side of | always |always for their own account, never for the org's| never |
147
150
148
-
Plus one branch outside the matrix: the target of an `org -> user`redirect from that org may transfer even once the org has an
149
-
admin - that redirect is our evidence that this particular person is the org. The branch has to check that the redirect points at
150
-
the caller; "a redirect exists for the org" would let every member of such an org walk in.
151
+
The matrix is the whole rule; probes have no exception. A redirect is about where money goes, not about who may act for an org,
152
+
and reading one as permission would tie a probe capability to a row either side can delete. Somebody who has to hand probes over
153
+
to an org that already has an admin asks that admin instead.
151
154
152
155
The dash cannot pre-compute the probes row - a member may not read the org's memberships, so it cannot tell whether the org has an
153
156
admin - so that rejection has to come back as its own error, distinguishable from "you are a viewer" and from "no such org", and
@@ -168,8 +171,8 @@ yields admin or member - so it is an explicit decision and it wins.
168
171
DB**. Once it has one the operations stop granting anything, which is what closes the door behind the first claimant. Safe against
169
172
the members cron, which only ever promotes - a manually assigned admin is never demoted.
170
173
171
-
The check and the promotion belong in the same transaction, with the org row locked: two members reading "no admin" at once would
172
-
otherwise both become one, and the door would not close behind either.
174
+
Two members transferring at the same moment both read "no admin" and both end up admins. Accepted: each of them was entitled to
175
+
claim the org at the moment they asked, so the outcome is one the rules already allow, just reached twice.
173
176
174
177
Known and accepted: an org that has not approved the `globalping` OAuth app is invisible to the memberships endpoint, so everybody
175
178
including its owners arrives through the public list with no role and is stored as a member (`github-api-client.ts:66`). Such an
@@ -198,8 +201,11 @@ migration never overwrites the first.
198
201
with it. The approval is not access - the token is - but it has to move for two reasons: `/applications/revoke` deletes from both
199
202
tables by the same `{ account_id, user_created }`, and an approval whose tokens left is invisible in the applications list while
200
203
its consent silently keeps skipping the consent screen. The collision is the thing to get right: the personal row becomes
201
-
`(user, app, org account)`, which the org may already have. Merge them, taking the union of the scopes, the way gp-auth itself
202
-
unions them (`model.ts:198`). `user_created` never changes - moving the row changes which account is billed, not who consented.
204
+
`(user, app, org account)`, which the org may already have. Then the org's row wins and the personal one is dropped - an approval
205
+
only decides whether the consent screen is shown the next time the app asks, and a transfer is nobody answering that screen, so
206
+
the scopes the org consented to are not for it to widen. Nothing breaks either way: gp-auth reads an approval only in
207
+
`saveAuthorizationCode`, never when a token is used. `user_created` never changes - moving the row changes which account is
208
+
billed, not who consented.
203
209
204
210
3.7 **Credits.** The balance and the deductions move to the org account, and the additions history moves with them
205
211
(`gp_credits_additions.github_id` becomes the org's), which carries the sponsor bonus along - `getUserBonus` sums the last twelve
@@ -213,8 +219,10 @@ inherits the percentage the donations earned, and the giver's timeline loses the
213
219
(`credits-timeline/src/index.ts:96`). It is safe to rewrite only because of 1.5 - the cron no longer recognises an addition by the
214
220
id it was filed under.
215
221
216
-
If an `org -> user` redirect pointed at the caller, transferring their credits deletes that row: the org now holds both the
217
-
history and the future sponsorship, and the redirect has nothing left to do.
222
+
Redirects are left alone. Handing over the credits that have arrived and routing the ones that will are two decisions, and the
223
+
`org -> user` row is the org's, not the caller's: clearing it reroutes the org's money, which is why it has its own endpoint and
224
+
its own confirmation (3.1). An org that still redirects to someone who just gave it their credits is a state the org ends, not a
225
+
side effect of their button.
218
226
219
227
3.8 **Failure.** Each endpoint is one transaction, so a failed transfer leaves its own entity kind untouched and says nothing
220
228
about the other two. The adoption token is the only part with an effect outside the database - the API's token map refreshes
@@ -253,13 +261,15 @@ and no screen to show it on.
253
261
254
262
## 6. Tests
255
263
256
-
6.1 **Unit**, per extension: the authorization matrix (3.2) with every cell and the redirect branch, the promotion window (3.3),
257
-
the approval merge (3.6), the credits traps (3.7), the subset validation (2.1).
264
+
6.1 **Unit**, per extension: the authorization matrix (3.2) with every cell, the promotion window (3.3), the subset validation
265
+
(2.1), the names the redirect list falls back to (3.1), and the two conditions around the adoption token (3.5) - a probe has to
266
+
have moved, and the entry has to carry a username. Not the credits and approval moves: those are unique keys, a trigger and what
267
+
MariaDB allows in a subquery, so a unit test there asserts the implementation's own statements back at it.
258
268
259
269
6.2 **e2e over REST**: each transfer alone and all three in sequence; a member refused the probes transfer in an org that has an
260
270
admin; the first member of an admin-less org becoming its admin; a second member transferring into the same org (the token array
261
-
grows, the first entry survives); a transfer into an org that already holds an approval of the same app (scopes union); a redirect
262
-
created, overwritten and deleted; a rollback on a forced failure leaving nothing moved; an org admin reading `id` and
271
+
grows, the first entry survives); a transfer into an org that already holds an approval of the same app (the org's row stands); a redirect
272
+
created, overwritten, refused by the side receiving it and deleted; a rollback on a forced failure leaving nothing moved; an org admin reading `id` and
263
273
`github_username` of the org's members and nothing else of them, while a member and a viewer read no co-member at all.
0 commit comments