From 5cb2ebed3f6d12ddf24c1f2139296481e10e4c32 Mon Sep 17 00:00:00 2001 From: Santi Balaguer Date: Wed, 27 May 2026 00:05:25 +0200 Subject: [PATCH 1/3] added broadcast method to RFC0020 --- .../0020-push-notification-subscriptions.md | 62 +++++++++++++++---- rust/crates/truapi/src/api/notifications.rs | 25 ++++++++ rust/crates/truapi/src/v01/notifications.rs | 42 +++++++++++++ .../truapi/src/versioned/notifications.rs | 3 + 4 files changed, 119 insertions(+), 13 deletions(-) diff --git a/docs/rfcs/0020-push-notification-subscriptions.md b/docs/rfcs/0020-push-notification-subscriptions.md index f7769cc4..1b83945c 100644 --- a/docs/rfcs/0020-push-notification-subscriptions.md +++ b/docs/rfcs/0020-push-notification-subscriptions.md @@ -2,7 +2,7 @@ title: "Push Notification Subscriptions" type: rfc status: draft -owner: "@pgherveou" +owner: ["@pgherveou", "@sbalaguer"] pr: --- @@ -14,6 +14,8 @@ Adds four TrUAPI methods — `push_add_rules`, `push_remove_rules`, `push_list_r The method names use `add` / `remove` rather than `subscribe` / `unsubscribe` because the `_subscribe` suffix is reserved for streaming TrUAPI methods (e.g. `statementStore.subscribe`). +An **interim transport**, `push_broadcast`, distributes announcements **without using the Statement Store as the distribution layer**, while still preserving the broadcaster's authenticity: the host signs the announcement with the calling product's account and submits it directly to the push backend, which verifies the signature and fans out using the same `(signer, topic)` rule matching. It is marked **(interim)** in the API and Types sections below. + ## References - Push notifications, original (v1, peer-to-peer): https://hackmd.io/@1JCaGppGSUqHtJilikYaKw/SyPN2yV6lx @@ -27,31 +29,27 @@ The push-notifications v2 design assigns delivery to a host-side notification sy ### Worked example: festival announcements -A conference product publishes festival-wide announcements as signed statements on a well-known topic. An attendee's app subscribes by calling `push_add_rules({ topics: [announcements_topic], signer: organizer_id })`, passing the organizer product's `ProductAccountId` explicitly. From that point on the user is woken up for new announcements even with the app closed: +A conference product publishes festival-wide announcements signed by the organizer. An attendee's app subscribes by calling `push_add_rules({ topics: [announcements_topic], signer: organizer_id })`, passing the organizer product's `ProductAccountId` explicitly. The organizer publishes with `push_broadcast` — the host signs the announcement and submits it to the backend. From that point on the attendee is woken for new announcements even with the app closed: ``` Publisher app Subscriber app (organizer side) (attendee side) | ^ | | | | - | (6) push | | (1) pushAddRules({ topics: [T], signer: organizer_id }) + | (5) push | | (1) push_add_rules({ topics: [T], signer: organizer_id }) | back to | | | caller | | | | v | +------------------------------------+---+------+ - | | Host | + | | Host + push backend | | | stores rule (organizer_id, T) | - | | -> deliver to this subscriber | - | +-----------------------+-----------------------+ - | ^ - | | (4) tail / match rule - | | - | +-----------------------+-----------------------+ - | | Statement Store | + | | (4) verify signature; match (organizer_id,T) | + | | -> deliver to this subscriber | | +-----------------------+-----------------------+ | ^ - | (2) compose signed statement | - |--- (3) statementStore.submit(statement) -+ + | (2) push_broadcast({ topics: [T], | (3) host signs the + | content }) | announcement and + |------------------------------------------+ submits to backend ``` ## Detailed Design @@ -66,6 +64,7 @@ Each TrUAPI method mirrors one backend endpoint: | `push_remove_rules` | `DELETE /v1/subscriptions/rules` | remove one or more rules | | `push_list_rules` | `GET /v1/subscriptions` | snapshot of currently active set | | `push_set_rules` | `PUT /v1/subscriptions/rules` | atomic replace of the full set | +| `push_broadcast` | direct submit _(interim)_ | publish a signed announcement | ```rust #[wire(request_id = 164)] @@ -89,6 +88,17 @@ async fn push_set_rules( ) -> Result>; ``` +#### Interim: direct broadcast + +`push_broadcast` distributes an announcement **without using the Statement Store as the distribution layer**, while preserving the broadcaster's authenticity. The product sends only `{ topics, content }`. The host **MUST** sign the broadcast with the calling product's account (it sets the signer to that account — unforgeable, host-set — and signs a canonical encoding of `(signer, topics, content)`) and submit it directly to the backend. The backend **MUST** verify the signature, rejecting any unsigned or invalidly-signed broadcast, then matches `(signer, topic)` against subscriber rules; matching, rate-limiting, dedup, and dispatch are unchanged — only the distribution layer differs. The signature is the basis of authenticity; the product neither supplies nor can forge it, which is why it appears in neither the request nor the response. + +```rust +#[wire(request_id = 172)] +async fn push_broadcast( + &self, cx: &CallContext, request: HostPushBroadcastRequest, +) -> Result>; +``` + ### Types `Topic` is reused from `v01::statement_store`. @@ -133,3 +143,29 @@ pub enum HostPushSetRulesError { Unknown { reason: String }, } ``` + +#### Interim: direct broadcast + +The broadcast is **not** a Statement Store statement: the requirement is a **verifiable signature**, not a store-shaped object, so there is no `channel`, topic slots, or `expiry`. A later version can move distribution to the Statement Store without changing subscriber rules or the authenticity model. + +```rust +pub struct PushBroadcastContent { + pub title: String, + pub body: String, + pub deeplink: Option, // route/URL to open on tap +} + +pub struct HostPushBroadcastRequest { + pub topics: Vec, // matched against subscriber rules (signer = caller) + pub content: PushBroadcastContent, +} + +pub struct HostPushBroadcastResponse { + pub message_hash: [u8; 32], // Blake2b-256 of the signed broadcast (dedup / audit) +} + +pub enum HostPushBroadcastError { + NotificationSystemUnavailable(String), + Unknown { reason: String }, +} +``` diff --git a/rust/crates/truapi/src/api/notifications.rs b/rust/crates/truapi/src/api/notifications.rs index 4068b87a..e6647147 100644 --- a/rust/crates/truapi/src/api/notifications.rs +++ b/rust/crates/truapi/src/api/notifications.rs @@ -2,6 +2,7 @@ use crate::versioned::notifications::{ HostPushAddRulesError, HostPushAddRulesRequest, HostPushAddRulesResponse, + HostPushBroadcastError, HostPushBroadcastRequest, HostPushBroadcastResponse, HostPushListRulesError, HostPushListRulesRequest, HostPushListRulesResponse, HostPushNotificationCancelError, HostPushNotificationCancelRequest, HostPushNotificationCancelResponse, HostPushNotificationError, HostPushNotificationRequest, @@ -153,4 +154,28 @@ pub trait Notifications: Send + Sync { cx: &CallContext, request: HostPushSetRulesRequest, ) -> Result>; + + /// Publish an announcement to subscribers. Interim distribution that does + /// not use the Statement Store as the distribution layer, while preserving + /// the broadcaster's authenticity: the host signs the announcement with the + /// calling product's account and submits it directly to the push backend, + /// which verifies the signature and fans out using the same `(signer, + /// topic)` rule matching. + /// + /// ```ts + /// const result = await truapi.notifications.pushBroadcast({ + /// topics: ["0x00"], + /// content: { title: "Web3 Summit", body: "Keynote moved to Hall A" }, + /// }); + /// result.match( + /// (value) => console.log(value.matched), + /// (error) => console.error(error), + /// ); + /// ``` + #[wire(request_id = 172)] + async fn push_broadcast( + &self, + cx: &CallContext, + request: HostPushBroadcastRequest, + ) -> Result>; } diff --git a/rust/crates/truapi/src/v01/notifications.rs b/rust/crates/truapi/src/v01/notifications.rs index ab1477a8..14803685 100644 --- a/rust/crates/truapi/src/v01/notifications.rs +++ b/rust/crates/truapi/src/v01/notifications.rs @@ -144,3 +144,45 @@ pub enum HostPushSetRulesError { /// Catch-all. Unknown { reason: String }, } + +/// Structured announcement content rendered on the device. Plaintext — +/// announcements are authenticity-only, not confidential. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct PushBroadcastContent { + /// Notification title. + pub title: String, + /// Notification body. + pub body: String, + /// Route or URL to open on tap. + pub deeplink: Option, +} + +/// Request to publish an announcement to subscribers via the interim direct +/// transport. The host signs the announcement with the calling product's +/// account and submits it directly to the push backend; the product supplies +/// only the topics and content. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct HostPushBroadcastRequest { + /// Topics to publish on; matched against subscriber rules with the caller + /// as signer. + pub topics: Vec, + /// Announcement content carried to the device. + pub content: PushBroadcastContent, +} + +/// Result of a successful [`HostPushBroadcastRequest`]. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct HostPushBroadcastResponse { + /// Blake2b-256 hash of the signed broadcast, for dedup and audit. + pub message_hash: [u8; 32], +} + +/// Failure modes for [`HostPushBroadcastRequest`]. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub enum HostPushBroadcastError { + /// The notification system is currently unavailable; nothing was published. + /// The product MAY retry later. + NotificationSystemUnavailable(String), + /// Catch-all. + Unknown { reason: String }, +} diff --git a/rust/crates/truapi/src/versioned/notifications.rs b/rust/crates/truapi/src/versioned/notifications.rs index 8c3ed058..49ae381a 100644 --- a/rust/crates/truapi/src/versioned/notifications.rs +++ b/rust/crates/truapi/src/versioned/notifications.rs @@ -21,4 +21,7 @@ versioned_type! { pub enum HostPushSetRulesRequest { V1 => v01::HostPushSetRulesRequest } pub enum HostPushSetRulesResponse { V1 } pub enum HostPushSetRulesError { V1 => v01::HostPushSetRulesError } + pub enum HostPushBroadcastRequest { V1 => v01::HostPushBroadcastRequest } + pub enum HostPushBroadcastResponse { V1 => v01::HostPushBroadcastResponse } + pub enum HostPushBroadcastError { V1 => v01::HostPushBroadcastError } } From 8f3e7187c8ec63be95d0f986145c7fe2f5c42e6c Mon Sep 17 00:00:00 2001 From: Santi Balaguer Date: Wed, 27 May 2026 11:19:40 +0200 Subject: [PATCH 2/3] edits --- .../0020-push-notification-subscriptions.md | 18 +++++++++--------- rust/crates/truapi/src/api/notifications.rs | 9 ++++----- rust/crates/truapi/src/v01/notifications.rs | 8 ++++---- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/rfcs/0020-push-notification-subscriptions.md b/docs/rfcs/0020-push-notification-subscriptions.md index 1b83945c..8373dabe 100644 --- a/docs/rfcs/0020-push-notification-subscriptions.md +++ b/docs/rfcs/0020-push-notification-subscriptions.md @@ -14,7 +14,7 @@ Adds four TrUAPI methods — `push_add_rules`, `push_remove_rules`, `push_list_r The method names use `add` / `remove` rather than `subscribe` / `unsubscribe` because the `_subscribe` suffix is reserved for streaming TrUAPI methods (e.g. `statementStore.subscribe`). -An **interim transport**, `push_broadcast`, distributes announcements **without using the Statement Store as the distribution layer**, while still preserving the broadcaster's authenticity: the host signs the announcement with the calling product's account and submits it directly to the push backend, which verifies the signature and fans out using the same `(signer, topic)` rule matching. It is marked **(interim)** in the API and Types sections below. +An **interim transport**, `push_broadcast`, distributes announcements **without using the Statement Store as the distribution layer**. The host submits the announcement to the push backend, **setting the publisher `signer` itself** (the product cannot override it), and the backend fans out using the same `(signer, topic)` rule matching. It is marked **(interim)** in the API and Types sections below. ## References @@ -29,7 +29,7 @@ The push-notifications v2 design assigns delivery to a host-side notification sy ### Worked example: festival announcements -A conference product publishes festival-wide announcements signed by the organizer. An attendee's app subscribes by calling `push_add_rules({ topics: [announcements_topic], signer: organizer_id })`, passing the organizer product's `ProductAccountId` explicitly. The organizer publishes with `push_broadcast` — the host signs the announcement and submits it to the backend. From that point on the attendee is woken for new announcements even with the app closed: +A conference product publishes festival-wide announcements signed by the organizer. An attendee's app subscribes by calling `push_add_rules({ topics: [announcements_topic], signer: organizer_id })`, passing the organizer product's `ProductAccountId` explicitly. The organizer publishes with `push_broadcast` — the host sets the `signer` to the organizer and submits the announcement to the backend. From that point on the attendee is woken for new announcements even with the app closed: ``` Publisher app Subscriber app @@ -43,13 +43,13 @@ Publisher app Subscriber app | +------------------------------------+---+------+ | | Host + push backend | | | stores rule (organizer_id, T) | - | | (4) verify signature; match (organizer_id,T) | + | | (4) match (organizer_id, T) | | | -> deliver to this subscriber | | +-----------------------+-----------------------+ | ^ - | (2) push_broadcast({ topics: [T], | (3) host signs the - | content }) | announcement and - |------------------------------------------+ submits to backend + | (2) push_broadcast({ topics: [T], | (3) host sets signer + | content }) | and submits to + |------------------------------------------+ the backend ``` ## Detailed Design @@ -90,7 +90,7 @@ async fn push_set_rules( #### Interim: direct broadcast -`push_broadcast` distributes an announcement **without using the Statement Store as the distribution layer**, while preserving the broadcaster's authenticity. The product sends only `{ topics, content }`. The host **MUST** sign the broadcast with the calling product's account (it sets the signer to that account — unforgeable, host-set — and signs a canonical encoding of `(signer, topics, content)`) and submit it directly to the backend. The backend **MUST** verify the signature, rejecting any unsigned or invalidly-signed broadcast, then matches `(signer, topic)` against subscriber rules; matching, rate-limiting, dedup, and dispatch are unchanged — only the distribution layer differs. The signature is the basis of authenticity; the product neither supplies nor can forge it, which is why it appears in neither the request nor the response. +`push_broadcast` distributes an announcement **without using the Statement Store as the distribution layer**. The product sends only `{ topics, content }`. The host **sets the `signer` itself** — to the calling product's channel identity, host-set so the product cannot override or spoof it — and submits the announcement to the backend. The backend matches `(signer, topic)` against subscriber rules; matching, rate-limiting, dedup, and dispatch are unchanged — only the distribution layer differs. The product never sets `signer`, which is why it is absent from the request. ```rust #[wire(request_id = 172)] @@ -146,7 +146,7 @@ pub enum HostPushSetRulesError { #### Interim: direct broadcast -The broadcast is **not** a Statement Store statement: the requirement is a **verifiable signature**, not a store-shaped object, so there is no `channel`, topic slots, or `expiry`. A later version can move distribution to the Statement Store without changing subscriber rules or the authenticity model. +The broadcast is **not** a Statement Store statement: it is a plain `{ topics, content }` the host submits with a host-set `signer`, so there is no `channel`, topic slots, or `expiry`. A later version can move distribution to the Statement Store without changing subscriber rules. ```rust pub struct PushBroadcastContent { @@ -161,7 +161,7 @@ pub struct HostPushBroadcastRequest { } pub struct HostPushBroadcastResponse { - pub message_hash: [u8; 32], // Blake2b-256 of the signed broadcast (dedup / audit) + pub message_hash: [u8; 32], // Blake2b-256 of the broadcast (dedup / audit) } pub enum HostPushBroadcastError { diff --git a/rust/crates/truapi/src/api/notifications.rs b/rust/crates/truapi/src/api/notifications.rs index e6647147..75a151aa 100644 --- a/rust/crates/truapi/src/api/notifications.rs +++ b/rust/crates/truapi/src/api/notifications.rs @@ -156,11 +156,10 @@ pub trait Notifications: Send + Sync { ) -> Result>; /// Publish an announcement to subscribers. Interim distribution that does - /// not use the Statement Store as the distribution layer, while preserving - /// the broadcaster's authenticity: the host signs the announcement with the - /// calling product's account and submits it directly to the push backend, - /// which verifies the signature and fans out using the same `(signer, - /// topic)` rule matching. + /// not use the Statement Store as the distribution layer: the host sets the + /// publisher `signer` to the calling product's identity (the product cannot + /// override it) and submits the announcement to the push backend, which fans + /// out using the same `(signer, topic)` rule matching. /// /// ```ts /// const result = await truapi.notifications.pushBroadcast({ diff --git a/rust/crates/truapi/src/v01/notifications.rs b/rust/crates/truapi/src/v01/notifications.rs index 14803685..176da77f 100644 --- a/rust/crates/truapi/src/v01/notifications.rs +++ b/rust/crates/truapi/src/v01/notifications.rs @@ -158,9 +158,9 @@ pub struct PushBroadcastContent { } /// Request to publish an announcement to subscribers via the interim direct -/// transport. The host signs the announcement with the calling product's -/// account and submits it directly to the push backend; the product supplies -/// only the topics and content. +/// transport. The host sets the publisher `signer` to the calling product's +/// identity and submits the announcement to the push backend; the product +/// supplies only the topics and content. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostPushBroadcastRequest { /// Topics to publish on; matched against subscriber rules with the caller @@ -173,7 +173,7 @@ pub struct HostPushBroadcastRequest { /// Result of a successful [`HostPushBroadcastRequest`]. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostPushBroadcastResponse { - /// Blake2b-256 hash of the signed broadcast, for dedup and audit. + /// Blake2b-256 hash of the broadcast, for dedup and audit. pub message_hash: [u8; 32], } From 4e2502c3122f6b4a8b1c999e0e53770ae68d9aa4 Mon Sep 17 00:00:00 2001 From: Santi Balaguer Date: Wed, 27 May 2026 12:54:35 +0200 Subject: [PATCH 3/3] makes signer mandatory on rules --- .../0020-push-notification-subscriptions.md | 12 +++--- rust/crates/truapi/src/api/notifications.rs | 14 ++++--- rust/crates/truapi/src/v01/notifications.rs | 37 +++++++++---------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/docs/rfcs/0020-push-notification-subscriptions.md b/docs/rfcs/0020-push-notification-subscriptions.md index 8373dabe..8c7bd2c8 100644 --- a/docs/rfcs/0020-push-notification-subscriptions.md +++ b/docs/rfcs/0020-push-notification-subscriptions.md @@ -10,7 +10,7 @@ pr: ## Summary -Adds four TrUAPI methods — `push_add_rules`, `push_remove_rules`, `push_list_rules`, `push_set_rules` — that mirror the rule-management endpoints of the [v2 push backend spec](https://hackmd.io/@1JCaGppGSUqHtJilikYaKw/r16YTVg5Ze). From the product's point of view a rule is just a `topic`: the product does not specify the signer, the host injects it when forwarding the rule to its push backend. The backend then delivers a push to the user's device(s) whenever a signed statement matching the resulting `(signer, topic)` pair appears on the Statement Store. The product never sees push tokens. +Adds four TrUAPI methods — `push_add_rules`, `push_remove_rules`, `push_list_rules`, `push_set_rules` — that mirror the rule-management endpoints of the [v2 push backend spec](https://hackmd.io/@1JCaGppGSUqHtJilikYaKw/r16YTVg5Ze). A rule is a `(signer, topic)` pair the product specifies in full: `signer` (mandatory) is the publisher whose statements should wake the user. The backend then delivers a push to the user's device(s) whenever a signed statement matching that `(signer, topic)` pair appears on the Statement Store. The product never sees push tokens. The method names use `add` / `remove` rather than `subscribe` / `unsubscribe` because the `_subscribe` suffix is reserved for streaming TrUAPI methods (e.g. `statementStore.subscribe`). @@ -25,7 +25,7 @@ This RFC exposes a TrUAPI-shaped surface over the rule-management API defined in ## Motivation -The push-notifications v2 design assigns delivery to a host-side notification system that tails the Statement Store, verifies signatures, and delivers pushes only for `(signer, topic)` pairs the user has whitelisted. TrUAPI needs a primitive that lets a product manipulate that whitelist. When `signer` is omitted the host defaults to the calling product's own identity; when provided explicitly the product can subscribe to statements from a different product. +The push-notifications v2 design assigns delivery to a host-side notification system that tails the Statement Store, verifies signatures, and delivers pushes only for `(signer, topic)` pairs the user has whitelisted. TrUAPI needs a primitive that lets a product manipulate that whitelist. `signer` is **mandatory** on every rule: the product always names the publisher it wants. ### Worked example: festival announcements @@ -103,13 +103,13 @@ async fn push_broadcast( `Topic` is reused from `v01::statement_store`. -A rule is a `(signer, topic)` pair. When `signer` is `None` the host injects the calling product's own identity; set it explicitly to subscribe to statements published by a different product. +A rule is a `(signer, topic)` pair. `signer` is **mandatory**: the subscriber always names the publisher. ```rust -pub struct HostPushAddRulesRequest { pub topics: Vec, pub signer: Option } -pub struct HostPushRemoveRulesRequest { pub topics: Vec, pub signer: Option } +pub struct HostPushAddRulesRequest { pub topics: Vec, pub signer: ProductAccountId } +pub struct HostPushRemoveRulesRequest { pub topics: Vec, pub signer: ProductAccountId } pub struct HostPushListRulesRequest; -pub struct HostPushSetRulesRequest { pub topics: Vec, pub signer: Option } +pub struct HostPushSetRulesRequest { pub topics: Vec, pub signer: ProductAccountId } pub struct HostPushListRulesResponse { pub topics: Vec, diff --git a/rust/crates/truapi/src/api/notifications.rs b/rust/crates/truapi/src/api/notifications.rs index 75a151aa..4806119d 100644 --- a/rust/crates/truapi/src/api/notifications.rs +++ b/rust/crates/truapi/src/api/notifications.rs @@ -74,15 +74,17 @@ pub trait Notifications: Send + Sync { request: HostPushNotificationCancelRequest, ) -> Result>; - /// Register one or more topics so the user is woken up by a push when a - /// signed statement matching any registered topic appears on the - /// Statement Store. Mirrors `POST /v1/subscriptions/rules` from the v2 - /// push backend spec. The signer is injected by the host (based on the - /// calling product's identity) when relaying the rule to the backend. + /// Register one or more `(signer, topic)` rules so the user is woken by a + /// push when a signed statement matching a rule appears on the Statement + /// Store. Mirrors `POST /v1/subscriptions/rules` from the v2 push backend + /// spec. `signer` is mandatory — the publisher whose statements should wake + /// the user (the calling product's own identity to self-subscribe, or + /// another product's). /// /// ```ts /// const result = await truapi.notifications.pushAddRules({ /// topics: ["0x00"], + /// signer: "0x…", /// }); /// result.match( /// () => console.log("ok"), @@ -102,6 +104,7 @@ pub trait Notifications: Send + Sync { /// ```ts /// const result = await truapi.notifications.pushRemoveRules({ /// topics: ["0x00"], + /// signer: "0x…", /// }); /// result.match( /// () => console.log("ok"), @@ -142,6 +145,7 @@ pub trait Notifications: Send + Sync { /// ```ts /// const result = await truapi.notifications.pushSetRules({ /// topics: ["0x00"], + /// signer: "0x…", /// }); /// result.match( /// () => console.log("ok"), diff --git a/rust/crates/truapi/src/v01/notifications.rs b/rust/crates/truapi/src/v01/notifications.rs index 176da77f..6985c8f8 100644 --- a/rust/crates/truapi/src/v01/notifications.rs +++ b/rust/crates/truapi/src/v01/notifications.rs @@ -49,20 +49,21 @@ pub struct HostPushNotificationCancelRequest { pub id: NotificationId, } -/// Request to register one or more topics the user wants to be woken up for. -/// Each topic is added independently; existing rules are not touched. +/// Request to register one or more `(signer, topic)` rules the user wants to be +/// woken up for. Each topic is added independently; existing rules are not +/// touched. /// -/// When `signer` is `None` the host injects the calling product's own -/// identity as the signer. Set `signer` explicitly to subscribe to -/// statements published by a *different* product (e.g. a conference -/// organizer's announcements). +/// `signer` is mandatory: the subscriber always names the publisher whose +/// statements should trigger a push — the calling product's own identity to +/// self-subscribe, or a *different* product's (e.g. a conference organizer's +/// announcements). #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostPushAddRulesRequest { /// Topics to register. pub topics: Vec, - /// Signer whose statements should trigger a push. Defaults to the - /// calling product's own identity when `None`. - pub signer: Option, + /// Publisher whose statements should trigger a push. Pass the calling + /// product's own identity to self-subscribe. + pub signer: ProductAccountId, } /// Failure modes for [`HostPushAddRulesRequest`]. @@ -77,15 +78,14 @@ pub enum HostPushAddRulesError { Unknown { reason: String }, } -/// Request to remove one or more previously registered topics. -/// Topics not currently active are ignored. +/// Request to remove one or more previously registered `(signer, topic)` rules. +/// Rules not currently active are ignored. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostPushRemoveRulesRequest { /// Topics to remove. pub topics: Vec, - /// Signer scope. When `None`, removes rules for the calling product's - /// own identity. - pub signer: Option, + /// Publisher scope of the rules to remove. Mandatory. + pub signer: ProductAccountId, } /// Failure modes for [`HostPushRemoveRulesRequest`]. @@ -122,15 +122,14 @@ pub enum HostPushListRulesError { } /// Atomic replace of the full topic set for the given signer with the -/// supplied vector. After a successful call, the active topics are exactly -/// `topics`. +/// supplied vector. After a successful call, the active topics for that signer +/// are exactly `topics`. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostPushSetRulesRequest { /// Topics that should be active after the call. pub topics: Vec, - /// Signer scope. When `None`, replaces rules for the calling product's - /// own identity. - pub signer: Option, + /// Publisher scope whose rule set is being replaced. Mandatory. + pub signer: ProductAccountId, } /// Failure modes for [`HostPushSetRulesRequest`].