diff --git a/overlay/oxide/plugins/RunicGateway.cs b/overlay/oxide/plugins/RunicGateway.cs
index fc17597..25c8752 100644
--- a/overlay/oxide/plugins/RunicGateway.cs
+++ b/overlay/oxide/plugins/RunicGateway.cs
@@ -4778,6 +4778,15 @@ namespace Oxide.Plugins
break;
case PermOpKind.EnsureMember:
+ // Before the read, because on Carbon the read is the thing that lies: a
+ // never-seen player's UserHasGroup reads the shared blank record (see
+ // HasNoCarbonRecord), so it can say "already a member" of nobody.
+ if (HasNoCarbonRecord(op.Subject))
+ {
+ job.Pending.Add(op.Subject + ":" + op.Object);
+ break;
+ }
+
if (permission.UserHasGroup(op.Subject, op.Object))
{
job.AlreadyCorrect++;
@@ -4800,6 +4809,15 @@ namespace Oxide.Plugins
if (!Registered(job, op.Object))
break;
+ // Never-seen on Carbon: the grant would land in the shared blank record and be
+ // held by every Steam id Carbon has no record for. It lands at the first sync
+ // after they join, as PLAN.md §29.8 already says of Carbon.
+ if (HasNoCarbonRecord(op.Subject))
+ {
+ job.NotLanded.Add(op.Subject + ":" + op.Object);
+ break;
+ }
+
if (HoldsDirectly(op.Subject, op.Object))
{
job.AlreadyCorrect++;
@@ -4942,6 +4960,24 @@ namespace Oxide.Plugins
return false;
}
+ ///
+ /// Whether this is Carbon and Carbon has no user record for the player.
+ ///
+ ///
+ /// Carbon (2.0.262) answers GetUserData for a Steam id it has never seen with ONE
+ /// shared, static blank record, and AddUserGroup and GrantUserPermission
+ /// write into whatever it returns. So a membership or grant for one never-seen player
+ /// goes to that shared record and is then held by every Steam id Carbon has no record
+ /// for, until the server restarts — and UserHasGroup reads it back as a success.
+ /// Oxide creates a real record instead, so this is never true there and Oxide's
+ /// behaviour is unchanged. Found in the account deletion walk (W3).
+ ///
+ ///
+ private bool HasNoCarbonRecord(string steamId)
+ {
+ return IsCarbon() && !permission.UserExists(steamId);
+ }
+
///
/// Whether a player holds a permission through a grant of their own, as opposed to
/// through a group.