diff --git a/overlay/oxide/plugins/RunicGateway.cs b/overlay/oxide/plugins/RunicGateway.cs index 7350e89..0fa0ad1 100644 --- a/overlay/oxide/plugins/RunicGateway.cs +++ b/overlay/oxide/plugins/RunicGateway.cs @@ -2775,6 +2775,13 @@ namespace Oxide.Plugins public readonly List Foreign = new List(); public readonly List Pending = new List(); + /// + /// Grants the store did not hold after being told to (subject:permission). + /// Reported rather than counted, so the site can never again be told a grant + /// landed that did not (D85). + /// + public readonly List NotLanded = new List(); + /// /// Group permissions an event lease holds (protocol 8). Neither applied to nor /// reported foreign; what the site asked for is kept on the hold instead. @@ -3138,12 +3145,22 @@ namespace Oxide.Plugins if (permission.GroupHasPermission(op.Subject, op.Object)) { job.AlreadyCorrect++; + break; } - else - { - permission.GrantGroupPermission(op.Subject, op.Object, this); + + // **`null`, not `this`, as the owner (D85).** With an owner, Oxide's grant + // first asks `PermissionExists(perm, owner)` — whether THAT plugin registered + // the name — and returns without a word when it did not. Every permission the + // site grants belongs to some other plugin, so from phase 7 until phase 13 this + // call did nothing on Oxide and was counted as applied (PLAN.md §27.6). The + // read-back is what makes the count true: only a grant the store now holds + // is reported as one. + permission.GrantGroupPermission(op.Subject, op.Object, null); + + if (permission.GroupHasPermission(op.Subject, op.Object)) job.GroupPermissions++; - } + else + job.NotLanded.Add(op.Subject + ":" + op.Object); break; @@ -3173,12 +3190,16 @@ namespace Oxide.Plugins if (HoldsDirectly(op.Subject, op.Object)) { job.AlreadyCorrect++; + break; } - else - { - permission.GrantUserPermission(op.Subject, op.Object, this); + + // The same owner trap as the group grant above (D85), and the same read-back. + permission.GrantUserPermission(op.Subject, op.Object, null); + + if (HoldsDirectly(op.Subject, op.Object)) job.Grants++; - } + else + job.NotLanded.Add(op.Subject + ":" + op.Object); break; @@ -3478,6 +3499,7 @@ namespace Oxide.Plugins report["foreign"] = job.Foreign; report["leased"] = job.Leased; report["pending"] = job.Pending; + report["notLanded"] = job.NotLanded; report["operations"] = job.Ops.Count; if (!string.IsNullOrEmpty(job.SetId)) @@ -3490,7 +3512,7 @@ namespace Oxide.Plugins $"ops={job.Ops.Count} grants={job.Grants} revokes={job.Revokes} " + $"groups={job.GroupsCreated} members={job.Members} same={job.AlreadyCorrect} " + $"unresolved={job.Unresolved.Count} foreign={job.Foreign.Count} " + - $"pending={job.Pending.Count} at={DateTime.UtcNow:HH:mm:ss}Z"; + $"pending={job.Pending.Count} notLanded={job.NotLanded.Count} at={DateTime.UtcNow:HH:mm:ss}Z"; EmitFrame(report); }