Social plugin architecture¶
cb-social-plugin is an interface package: it supplies action sinks for Bluesky, Mastodon and X.
It does not introduce a second event runtime or a special social rule engine.
Layers¶
ActionInvocation
|
+--> provider native action -----------------------> provider API
|
`--> <provider>.mirror_telegram_message
|
v
shared mirror contract
/ | \
/ | \
source key state DB Telegram media gateway
\ | /
\ | /
provider adapter ------------------------> provider API
The shared layer in cb_social_plugin.common owns source normalization, hashtag filtering,
source-version checks, durable publication mappings, Telegram album staging, image acquisition and
the common action-result shape. Provider modules own authentication and remote API semantics.
Shared mirror contract¶
Each provider exposes:
<provider>.mirror_telegram_message<provider>.flush_due_media_groups
The mirror accepts either a raw Telegram update or message. It derives a publication key from
chat/message identity (or media-group identity), computes a monotonically comparable source version
from Telegram timestamps/update ids, and optionally persists the remote mapping.
With state_db_path configured, an already-published source version is skipped before a remote
request. A newer Telegram edit is handed to the provider adapter with the existing mapping.
State database¶
The package uses a small SQLite database with three tables.
social_publications¶
Primary key: (provider, source_key).
It stores the remote id/URI/CID/record key when applicable, the latest Telegram source_version,
provider metadata, and update time. Provider names are part of the key, so the same Telegram message
can be mirrored independently to all three networks in one database.
social_media_groups¶
Primary key: (provider, source_key).
It stores a due timestamp and the normalized Telegram payloads staged for one album. Repeated identical payloads are deduplicated. A successful real flush persists the publication mapping and deletes the staged group; a dry-run flush reports due groups without deleting them.
social_publish_attempts¶
Primary key: (provider, source_key, source_version).
This is a durable write-ahead receipt for provider operations whose remote create boundary cannot
be made atomic with the local mapping. X uses it when durable state is configured. The row records a payload fingerprint,
expected text, previous remote post id for native edits, start time, and—once known—the remote post
id/URI. Promoting a successful publication into social_publications removes completed attempts up
to that source version.
Media acquisition pipeline¶
Telegram message(s)
-> select best photo / image document
-> deduplicate by file_unique_id or file_id
-> Telegram file gateway get_file
-> download bytes
-> provider upload API
-> attach returned media/blob ids to post
The gateway can be supplied by telegram_gateway_factory or created from a Telegram bot token.
When media exists but no gateway is available, the default behavior is to publish text and report a
media omission. media_required: true turns acquisition/upload omissions into failures instead.
The current mirror contract is image-only. Video/GIF publishing is intentionally not normalized across providers because those APIs have different asynchronous and chunked processing semantics.
Idempotency boundaries¶
The local publication mapping is an operational duplicate guard, not a distributed transaction. Provider idempotency or a durable recovery protocol is therefore required to close or deliberately fail closed around the remote-success/local-commit crash window.
| Provider | Remote behavior | Remaining boundary |
|---|---|---|
| Bluesky | deterministic record key + putRecord |
retry targets the same AT Protocol record |
| Mastodon | deterministic create Idempotency-Key; mapped edits target the same status |
server retention of idempotency keys is provider-defined |
| X | native edits can use edit_options.previous_post_id; creates have no documented idempotency key |
with state_db_path, persists an in-flight attempt before mutation, records returned Post IDs, and reconciles ambiguous commits read-only; stateless compatibility mode retains the narrow duplicate-create crash window |
These properties do not automatically make the Call Baxter action replay-eligible. The generic runtime's action replay evidence also requires durable executor idempotency and authorization provenance; the social mirror contracts intentionally remain fail-closed today. See Arbitrary actions and replay safety.
Error boundary¶
Provider clients call raise_for_status(). HTTP errors, including rate-limit responses, therefore
propagate to the Call Baxter action boundary rather than being converted into apparent success.
Call Baxter records the executor failure and applies normal on_error / stop_on_error semantics.