TaskChampion
repository·main·Indexed 41 days ago
https://github.com/gothenburgbitfactory/taskchampionA task storage and synchronization engine that powers Taskwarrior, providing Rust and C APIs for managing task replicas. It defines a formal specification for task synchronization protocols, including support for AEAD encryption (CHACHA20_POLY1305), snapshot management to reduce storage bloat, and a replica-based storage model using transaction-protected storage engines.
What's inside TaskChampion
- TaskChampion is a library that implements the task storage and synchronization logic used by Taskwarrior. It provides both Rust and C APIs, enabling developers to build applications that maintain and manipulate their own task replicas. It also defines a formal specification for tasks and synchronization protocols, which allows for alternative implementations of task servers or replicas to interoperate.
Overview of TaskChampion
mainTaskChampion provides the task storage and synchronization logic used by Taskwarrior. It is designed to allow any application to maintain and manipulate its own replica of tasks. It provides both Rust and C APIs and includes a formal specification for tasks and synchronization protocols, which enables developers to build alternative implementations of task replicas or task servers.What is a TaskChampion Replica?
mainAReplicais the core abstraction in TaskChampion representing a local copy of a user's task data. Multiple replicas (e.g., on a laptop and a phone) can exist simultaneously and synchronize with one another to maintain consistency. You interact with the task database through theReplicatype.Understand the Server-Replica Protocol
mainThe Server-Replica Protocol is an abstract request/response transaction model used to synchronize task histories between clients (replicas) and a server.
Core Concepts
- Clients: Any replica accessing the same task history is considered a client. Multiple clients can replicate a single task history.
- Server: Responsible for storing the task history as a branch-free sequence of versions and storing the latest snapshot. To the server, both versions and snapshots are opaque byte sequences.
- Version Invariant: The protocol enforces a single chain of versions. Every version must have no more than one parent and one child, and exactly one version in the chain may have zero parents (the root) and exactly one version may have zero children (the tip).
Read data from the Task Database
mainThe Task Database provides read access to data within the replica's storage via methods on the Task Database struct.
Note on Consistency: Each read operation is executed within its own transaction. Consequently, data may not be perfectly consistent between two separate read operations. For TaskChampion's typical use cases, this behavior is acceptable.
Interoperability with Taskwarrior
mainTaskChampion is the core engine behind Taskwarrior (since version 3.0). Because TaskChampion provides a standardized specification for tasks and synchronization, you can use it to build third-party applications that interoperate with Taskwarrior. This interoperability can happen either by sharing the same task replica or by using the synchronization protocol.Configure Task Storage
mainReplicas access task data through aStorageobject. Thestoragemodule provides a pluggable interface, allowing you to use built-in implementations or provide your own. Common implementations includeSqliteStoragefor local disk storage andIndexedDbStoragefor WASM/browser environments.Synchronize Replicas with a Server
mainSynchronization is performed by a
Replicaagainst a server. To set up synchronization:- Define a server using
ServerConfig. - Convert the configuration into a server instance using
.into_server().await?. - Call
replica.sync(&mut server, force_sync).await?to perform the synchronization.
- Define a server using
Understand TaskChampion synchronization and replicas
mainTaskChampion supports synchronization of replicas that contain the same set of tasks.
Key concepts:
- Replica: A local instance of a task list. Replicas can perform all operations locally without an active connection to a sync server.
- Synchronization: The process of sharing local operations between replicas once they connect to a sync server. This allows users to maintain and update the same task list across multiple devices without requiring a constant internet connection.
How encryption and key derivation work in TaskChampion
mainTaskChampion uses an encryption secret of arbitrary length provided in the client configuration to protect data sent to the server (specifically versions and snapshots).
Key Derivation The client derives a 32-byte encryption key from your secret using:
- Algorithm: PBKDF2 with HMAC-SHA256
- Iterations: 600,000
- Salt: Implementation-dependent (varies by protocol context)
Encryption Algorithm Data is encrypted using AEAD with the CHACHA20_POLY1305 algorithm. It is critical that a random nonce is generated for every operation, as reusing a nonce with the same key compromises security.
Scope Encryption is applied to all data sent from the local host to the server. It is not used for local (on-disk) synchronization.
The structure of encrypted byte-streams
mainWhen data is encrypted and prepared for transmission, it follows a specific byte-stream format. This format allows the client to identify the data version and the nonce required for decryption.
The byte-stream structure is:
Offset Field Size Description 0 version1 byte Format version (currently always 1)1 nonce12 bytes The encryption nonce 13 ciphertextRemaining The concatenated ciphertext and authentication tag Note: The
ciphertextfield follows the pattern used in BoringSSL'sEVP_AEAD_CTX_seal, where the ciphertext and tags are concatenated into a single bytestring.How the TaskChampion synchronization model works
mainTaskChampion uses a synchronization model based on Operational Transformation (OT) to manage task data across disconnected replicas.
Core Principles
- Disconnected Replicas: Replicas never communicate directly; they synchronize via a central server.
- Opaque Server: The server does not see task data; it only handles opaque blobs and metadata to manage synchronization.
- Linearization: Synchronization aims to bring all replicas to a single, linear sequence of operations and a shared state.
- Rebase, Don't Merge: Unlike Git, operations cannot be merged. Instead, local operations must be "rebased" onto the latest server version using OT.
- No Force Push: Once an operation is sent to the server, it cannot be changed.
The Replica Invariant
A replica maintains its current state by applying its sequence of un-synchronized operations to its last known "base version" (the last version successfully synchronized with the server).