INVITE ONLY
OBSERVATORY

10.1 GlyconCache

A cache that seals at write and forgets on time.

GLYCONCACHE

An encrypted cache for the hot path: entries sealed at write, expired by design, gone on schedule.

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.

The cache contract

Read the seal-at-write path in the source that ships

backend/src/glycon.rs — excerpt
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)
}

Check every parameter against the file it came from

Cache parameters — values from source
ParameterValueSource
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

24 h1

the longest-lived cache entry class (sealed-send audit)

5 min2

sweep cadence pruning expired audit rows

Take the memory dump. Read what it yields.

Category classes only — never named products
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

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.

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.

The same destruction signal that erases the ledger row fans out to the cache purge — one transaction window, no orphaned copies.

Delivery state lives minutes; audit entries live 24 hours; everything carries a TTL because the design forgets on schedule.

The hot path is where plaintext leaks happen in classical stacks. Sealing the fast layer closes the easiest seizure surface.

Get QERYX Read the docs