calternal_plugin::changes
One durable change sequence per User (DESIGN §§24, 58; #668).
This extends the Files feed’s transaction-and-wakeup model. Producers append IDs and revisions inside the transaction that publishes their projection or receipt. They wake the User only after commit. Push carries only a sequence; a lost wakeup is repaired by the stream’s periodic head check. The journal keeps at most 10,000 rows per User. Readers scan at most 100 rows, never an entire Home. After expiry or an access change, clients discard retained data and rescan only their bounded visible windows. No path is a public identity.
access_key is internal authority data, not a client projection. Each Plugin
must check current access and return its current projection on every read.
Missing adapters fail closed. This layer does not own caches or receipts.
Source: crates/calternal-plugin/src/changes.rs
Structs
Section titled “Structs”Change
Section titled “Change”pub struct ChangeOne current item revision or a deletion tombstone; bodies remain lazy.
Fields
pub sequence: i64pub plugin_id: Stringpub item_id: Stringpub revision: Stringpub deleted: boolpub projection: Option<serde_json::Value>: A small header projection, produced by the owning Plugin after access checks.
Implements: Clone, Debug, Serialize, Deserialize, ToSchema
Source: crates/calternal-plugin/src/changes.rs:45
pub struct PageOne bounded catch-up page. An absent cursor request establishes a baseline.
Fields
pub entries: Vec<Change>pub cursor: Stringpub sequence: i64pub has_more: bool
Implements: Debug, Serialize, Deserialize, ToSchema
Source: crates/calternal-plugin/src/changes.rs:102
Record
Section titled “Record”pub struct RecordInternal journal row. Only an access-checked Change reaches a client.
Fields
pub sequence: i64pub plugin_id: Stringpub item_id: Stringpub revision: Stringpub deleted: boolpub access_key: String
Implements: Clone, Debug
Record::tombstone
Section titled “Record::tombstone”pub fn tombstone(&self) -> ChangeA denied or deleted item retains only its previously granted identity.
Source: crates/calternal-plugin/src/changes.rs:34
Subscription
Section titled “Subscription”pub struct SubscriptionOne User’s stream signal; multiple Installations share it, never other Users.
Fields
pub signal: Arc<Notify>
Implements: Drop
Subscription::new
Section titled “Subscription::new”pub fn new(user: &str) -> SelfSubscribe before reading the head so a commit cannot race stream setup.
Source: crates/calternal-plugin/src/changes.rs:323
pub enum ErrorProtocol errors are content-free and distinct from expired authority.
Variants
InvalidExpiredStorage(sqlx::Error)
Implements: Debug, From<sqlx::Error>
Source: crates/calternal-plugin/src/changes.rs:89
Type aliases
Section titled “Type aliases”ProjectionFuture
Section titled “ProjectionFuture”pub type ProjectionFuture<'a> = std::pin::Pin< Box<dyn std::future::Future<Output = Result<Option<Change>, sqlx::Error>> + Send + 'a>,>;Each namespace can add an access-checked header without duplicating protocol logic.
Source: crates/calternal-plugin/src/changes.rs:70
Functions
Section titled “Functions”append
Section titled “append”pub async fn append( connection: &mut SqliteConnection, user: &str, record: &Record,) -> Result<i64, Error>Append at the producer’s commit boundary. Rollback removes the sequence too.
Call wake_user after commit, never while the transaction is uncommitted.
Source: crates/calternal-plugin/src/changes.rs:123
authority
Section titled “authority”pub async fn authority(db: &Db, user: &str, scope: &str) -> Result<String, sqlx::Error>Include durable access epochs in cursor authority; instance toggles use one epoch.
Source: crates/calternal-plugin/src/changes.rs:170
pub async fn head(db: &Db, user: &str) -> Result<i64, sqlx::Error>Read only this User’s high-water mark. Another User cannot advance it.
Source: crates/calternal-plugin/src/changes.rs:159
install_files_bridge
Section titled “install_files_bridge”pub async fn install_files_bridge(db: &Db) -> Result<(), sqlx::Error>Install the Files proving bridge after both migration sets exist. Existing Files-only consumers do not need the app journal (#668; DESIGN §§24, 58). Startup has one writer; installing twice leaves the same trigger contract. Sharing uses a read view and stored grants. Refresh grant triggers so an upgraded bridge invalidates Group members as well as direct Users (#867).
Source: crates/calternal-plugin/src/changes.rs:378
invalidate_access
Section titled “invalidate_access”pub async fn invalidate_access( connection: &mut SqliteConnection, user: &str,) -> Result<(), sqlx::Error>Change the authority epoch in the same transaction as revoke or scope changes. Clients must discard all retained data before rebuilding bounded windows.
Source: crates/calternal-plugin/src/changes.rs:149
migrations
Section titled “migrations”pub fn migrations() -> MigrationSetShared journal migrations use their own namespace, not a Plugin’s numbers.
Source: crates/calternal-plugin/src/changes.rs:110
pub async fn read<F>( db: &Db, key: &[u8; 32], user: &str, scope: &str, token: Option<&str>, project: F,) -> Result<Page, Error>where F: for<'a> Fn(&'a Record) -> ProjectionFuture<'a>,Fixed work read, even when every scanned row is denied. Recheck authority at the route after projection reads so a concurrent revoke cannot publish content.
Source: crates/calternal-plugin/src/changes.rs:218
wake_user
Section titled “wake_user”pub fn wake_user(user: &str)Notify only active streams for this User after durable commit. Periodic head checks repair missed notifications, including trigger-only publication.
Source: crates/calternal-plugin/src/changes.rs:362
Constants
Section titled “Constants”PAGE_BYTES
Section titled “PAGE_BYTES”pub const PAGE_BYTES: usizeMaximum encoded response, including the cursor and envelope.
Source: crates/calternal-plugin/src/changes.rs:28
PAGE_ROWS
Section titled “PAGE_ROWS”pub const PAGE_ROWS: usizeMaximum journal rows scanned and returned by a single request.
Source: crates/calternal-plugin/src/changes.rs:26
RETENTION_SECONDS
Section titled “RETENTION_SECONDS”pub const RETENTION_SECONDS: i64Maximum time a cursor can cover without a bounded window rescan.