10.1 GlyconCache
A cache that seals at write and forgets on time.
An encrypted cache for the hot path: entries sealed at write, expired by design, gone on schedule.
- Value AEAD ChaCha20-Poly1305
- Key derivation HKDF-SHA512
- Key-name hashing HMAC-SHA3-256
01 / THE HOT PATH — SEALED AT WRITE
Follow a value into the fast layer. It seals before it lands.
The encryption boundary
GlyconCache is the fast layer: sealed envelopes awaiting delivery, session state, and rate-limit counters, spoken over the common cache wire protocol. Every value is sealed with ChaCha20-Poly1305 before it lands, under a key derived once at boot via HKDF-SHA512 from a master secret that never touches disk. Key names that would identify you are replaced by keyed HMAC-SHA3-256 digests — a cache dump alone cannot be walked back to profile or conversation identifiers.
- Value seal
- ChaCha20-Poly1305, random 96-bit nonce per write
- Stored format
- nonce (12 B) || ciphertext || tag (16 B)
- Key names
- keyed HMAC-SHA3-256, 128-bit truncated — no unkeyed path
- Counters
- bare integers under hashed names; abuse control without a user ledger
Deletion reaches the cache
The same destruction signal that erases a ledger row fans out to the cache: a purge listener walks every per-conversation namespace and deletes matching entries in the same window. Cache residue does not outlive the message it served.
Nothing here is a system of record
Eviction is always safe — that is itself a privacy property. Every entry carries an expiry: delivery state lives minutes, sealed-send audit entries live 24 hours and are swept every five minutes. The design forgets on schedule, so a seizure of the hot path yields only what the current TTL window holds, sealed.
Eviction is always safe. Nothing here is a system of record.
02 / THE SOURCE — VERBATIM
Read the seal-at-write path in the source that ships
const GLYCON_DOMAIN: &[u8] = b"GLYCONCACHE-ENCRYPT-v1";
fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, GlyconError> {
let cipher = ChaCha20Poly1305::new_from_slice(&self.encryption_key)
.map_err(|_| GlyconError::CipherInit)?;
let mut nonce_bytes = [0u8; NONCE_SIZE];
getrandom::getrandom(&mut nonce_bytes)
.map_err(|_| GlyconError::Encryption)?;
let nonce = GenericArray::from_slice(&nonce_bytes);
let mut buffer = plaintext.to_vec();
cipher.encrypt_in_place(nonce, b"", &mut buffer)
.map_err(|_| GlyconError::Encryption)?;
// Output: nonce (12) || ciphertext+tag
let mut output = Vec::with_capacity(NONCE_SIZE + buffer.len());
output.extend_from_slice(&nonce_bytes);
output.extend_from_slice(&buffer);
Ok(output)
}
03 / THE PARAMETERS — READ FROM SOURCE
Check every parameter against the file it came from
| Parameter | Value | Source |
|---|---|---|
| Value AEAD | ChaCha20-Poly1305 (RFC 8439) | backend/src/glycon.rs |
| Nonce | 96-bit, fresh random per write | backend/src/glycon.rs |
| Cache key derivation | HKDF-SHA512, domain GLYCONCACHE-ENCRYPT-v1 | backend/src/glycon.rs |
| Key-name hashing | keyed HMAC-SHA3-256, 128-bit truncated | backend/src/glycon.rs |
| Sealed-send audit TTL | 24 hours | backend/src/services/sealed_audit_sweep.rs |
| Audit sweep cadence | every 5 minutes | backend/src/services/sealed_audit_sweep.rs |
| Purge signal | ledger destruction trigger, LISTEN fsor_purge | backend/src/services/lifecycle.rs |
| Encryption key lifetime | zeroized on drop | backend/src/glycon.rs |
04 / THE YIELD — BY CACHE DISCIPLINE
Take the memory dump. Read what it yields.
| QERYX GlyconCache | Classical cache layer | No discipline | |
|---|---|---|---|
| Real workloads | |||
| Memory-dump seizure | Sealed entries inside TTL | Plaintext sessions and queues | Plaintext everything |
| Residue after delete | Purged on the ledger's signal | Stale keys linger | Unbounded |
| Rate limiting | Hashed, quantized counters | Per-user counters | Per-user logs |
| TTL discipline | Everything expires | Optional | Not included |
05 / THE HARD QUESTIONS
Ask what the cache still holds.
What is cached, exactly?
Sealed envelopes awaiting delivery, session state, and rate-limit counters — each with an expiry. Values are sealed at write; the counters hold only integers under keyed-hash names.
What can a cache-memory seizure yield?
Sealed entries within their TTL window. Content is under the same end-to-end stack as everything else — reading it requires breaking both X25519 and ML-KEM-1024.
How does deletion reach the cache?
The same destruction signal that erases the ledger row fans out to the cache purge — one transaction window, no orphaned copies.
How long does anything live here?
Delivery state lives minutes; audit entries live 24 hours; everything carries a TTL because the design forgets on schedule.
Why an encrypted cache at all?
The hot path is where plaintext leaks happen in classical stacks. Sealing the fast layer closes the easiest seizure surface.