feat(shared-topics): GroupPubsubService + shared publish fan-out + SDK handle (D2)

The runtime for shared topics:
- pubsub_repo: fan_out_publish gains `AND t.shared = FALSE` (a shared
  trigger never fires on a per-app publish); new fan_out_shared_publish
  matches `shared = true` pubsub triggers on the resolved owning group,
  each delivery stamped with the writer app_id (M2 model).
- GroupPubsubService trait (shared) + GroupPubsubServiceImpl: resolve the
  owning group (kind='topic') from cx.app_id's chain, require editor+
  (GroupPubsubPublish, fails closed for anon), size-cap, fan out.
- SDK: `pubsub::shared_topic("name")` -> GroupTopicHandle with
  `.publish(subtopic, msg)` / `.publish(msg)`; wired through Services +
  the picloud binary (reuses the one PubsubRepo).
- authz: Capability::GroupPubsubPublish(GroupId), editor+ / script:write.

The owning-group chain walk is the isolation boundary; a sibling-subtree
app never resolves the topic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-02 21:48:59 +02:00
parent 1c7e8886d8
commit 9626d15863
10 changed files with 430 additions and 43 deletions

View File

@@ -19,6 +19,7 @@ pub mod group;
pub mod group_docs;
pub mod group_files;
pub mod group_kv;
pub mod group_pubsub;
pub mod http;
pub mod ids;
pub mod inbox;
@@ -71,6 +72,7 @@ pub use group::Group;
pub use group_docs::{GroupDocsError, GroupDocsService, NoopGroupDocsService};
pub use group_files::{GroupFilesError, GroupFilesService, NoopGroupFilesService};
pub use group_kv::{GroupKvError, GroupKvService, NoopGroupKvService};
pub use group_pubsub::{GroupPubsubError, GroupPubsubService, NoopGroupPubsubService};
pub use http::{HttpError, HttpRequest, HttpResponse, HttpService, NoopHttpService};
pub use ids::{
AdminUserId, ApiKeyId, AppId, AppUserId, ExecutionId, GroupId, InvitationId, QueueMessageId,