premium: publish the relay posting decision, not its inputs

GET /premium/status gains write_allowlisted + can_post_general.

The wallet was re-deriving posting rights from write_policy + premium, which
cannot see RELAY_WRITE_ALLOWLIST_NPUBS. An allowlisted operator was shown
"posting needs a premium subscription" with the composer hidden, while the
relay's own admission check would have accepted the event. Compute the answer
server-side from the same RelayProvider the admission service consults so the
two cannot drift.
This commit is contained in:
jwinterm
2026-07-31 17:17:58 -04:00
parent 0f0ec2af49
commit ae118edf86
2 changed files with 67 additions and 1 deletions
+11 -1
View File
@@ -4991,19 +4991,29 @@
"StatusResp": {
"type": "object",
"required": [
"active"
"active",
"write_allowlisted",
"can_post_general"
],
"properties": {
"active": {
"type": "boolean",
"description": "Whether the caller currently holds active premium."
},
"can_post_general": {
"type": "boolean",
"description": "Whether the caller may publish general (non-wallet) events to this\ninstance's relay RIGHT NOW.\n\nThe authority on this is [`crate::infra::relay::policy::decide`], which\nruns server-side at admission. Clients previously re-derived it from\n`write_policy` + premium alone and so had no way to know about the\noperator write-allowlist: an allowlisted operator was told \"premium\nrequired\" and the composer was hidden, even though the relay would have\naccepted the event. Publish the decision instead of the inputs."
},
"premium_until": {
"type": [
"string",
"null"
],
"description": "Premium expiry (RFC3339), or null if never premium / lapsed."
},
"write_allowlisted": {
"type": "boolean",
"description": "Whether the caller's linked npub is on the operator write-allowlist\n(`RELAY_WRITE_ALLOWLIST_NPUBS`), which permits publishing ANY kind\nregardless of policy or premium."
}
}
},