Persist exact transport replay

This commit is contained in:
Meta Vens 2026-08-24 16:39:17 +02:00
commit 9da6070faa
3 changed files with 266 additions and 78 deletions

View file

@ -17,11 +17,16 @@ service core, the private Codex-auth/provider backend, and one public daemon
entrypoint. The daemon reads one typed CultCache configuration document, binds
only loopback, uses CultNet's four-byte big-endian direct-pipe framing around
the encrypted MessagePack envelope, bounds connection threads and frame sizes,
and keeps provider execution outside the service-state lock. The core admits
distinct caller keys and policies,
returns an exact cached response for byte-identical retries, refuses conflicting
or concurrent reuse of one caller/request identity, and leaves provider
execution outside its lock. The backend verifies the exact child binary and
and keeps provider execution outside the service-state lock. One keyed
CultCache replay record is sealed active before provider I/O and replaced by
the exact encrypted response before socket reply. Completed retries survive
restart byte-for-byte; a restart-era active claim refuses as indeterminate
rather than executing twice. Replay identity is never expired implicitly.
Connection-key rotation is detected through an explicit non-secret epoch and
refuses startup until the caller identity or replay authority is deliberately
retired. The core
admits distinct caller keys and policies and refuses conflicting or concurrent
reuse of one caller/request identity. The backend verifies the exact child binary and
reported Codex home, limits child RPC to authentication, reads API-key or
ChatGPT credentials without publishing their identity, retries one ChatGPT 401
only after the official writer advances the credential store, and lowers raw
@ -31,10 +36,10 @@ No upstream Codex crate is linked into the package. The official binary is a
pinned deployment input, keeping Codex's application build graph outside both
consumers and this small daemon.
This is not yet a deployable replacement for the live connector. Persistent
replay recovery, redacted CultMesh/Odin publication, shared consumer client
cuts, and the independent Idunn target remain open. The old daemon remains the
sole live credential writer until those proofs land.
This is not yet a deployable replacement for the live connector. Redacted
CultMesh/Odin publication, shared consumer client cuts, and the independent
Idunn target remain open. The old daemon remains the sole live credential
writer until those proofs land.
## Contract invariant

View file

@ -18,7 +18,7 @@ use crate::{
CodexTransportAdmission, CodexTransportEnvelope, CodexTransportService, ServiceError,
};
const CONFIG_EPOCH: u32 = 1;
const CONFIG_EPOCH: u32 = 2;
const CONFIG_KEY: &str = "runtime";
const FRAME_OVERHEAD_BUDGET: usize = 4096;
@ -26,6 +26,7 @@ const FRAME_OVERHEAD_BUDGET: usize = 4096;
pub struct CodexCallerConfig {
pub caller_runtime_id: String,
pub connection_key_file: PathBuf,
pub connection_key_epoch: u32,
pub allowed_models: Vec<String>,
pub max_concurrent_requests: usize,
pub max_payload_bytes: usize,
@ -55,6 +56,8 @@ pub struct CodexDaemonConfig {
pub max_expiry_skew_ms: u64,
#[cultcache(key = 9)]
pub callers: Vec<CodexCallerConfig>,
#[cultcache(key = 10)]
pub replay_store: PathBuf,
}
impl CodexDaemonConfig {
@ -72,6 +75,7 @@ impl CodexDaemonConfig {
if self.codex_executable.as_os_str().is_empty()
|| self.codex_executable_sha256 == [0; 32]
|| self.codex_home.as_os_str().is_empty()
|| self.replay_store.as_os_str().is_empty()
|| self.max_frame_bytes < FRAME_OVERHEAD_BUDGET * 2
|| self.max_frame_bytes > u32::MAX as usize
|| self.max_connections == 0
@ -87,6 +91,7 @@ impl CodexDaemonConfig {
|| caller.caller_runtime_id.trim() != caller.caller_runtime_id
|| !caller_ids.insert(caller.caller_runtime_id.as_str())
|| caller.connection_key_file.as_os_str().is_empty()
|| caller.connection_key_epoch == 0
|| caller.allowed_models.is_empty()
|| caller
.allowed_models
@ -140,7 +145,8 @@ pub fn write_daemon_config(
pub fn serve(config_path: &Path) -> Result<(), CodexDaemonError> {
let config = load_daemon_config(config_path)?;
let bind = config.validate()?;
let service = Arc::new(Mutex::new(CodexTransportService::new(
let service = Arc::new(Mutex::new(CodexTransportService::open(
&config.replay_store,
load_admissions(&config)?,
config.max_expiry_skew_ms,
)?));
@ -208,6 +214,7 @@ fn load_admissions(
CodexCallerAdmission::new(
caller.caller_runtime_id.clone(),
secret.to_string(),
caller.connection_key_epoch,
caller.allowed_models.clone(),
caller.max_concurrent_requests,
caller.max_payload_bytes,
@ -372,11 +379,13 @@ mod tests {
callers: vec![CodexCallerConfig {
caller_runtime_id: "epiphany-yggdrasil".to_string(),
connection_key_file: root.join("epiphany.key"),
connection_key_epoch: 1,
allowed_models: vec!["gpt-5.4".to_string()],
max_concurrent_requests: 4,
max_payload_bytes: 512 * 1024,
max_output_tokens: 32_768,
}],
replay_store: root.join("replay.cc"),
}
}

View file

@ -1,7 +1,9 @@
use std::collections::{HashMap, HashSet};
use std::path::Path;
use aes_gcm::aead::{Aead, Payload};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use cultcache_rs::{CultCache, DatabaseEntry, OwnedRedbMessagePackBackingStore};
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
@ -437,6 +439,7 @@ pub enum CodexRefusal {
Policy,
Capacity,
InFlight,
Indeterminate,
ReplayConflict,
Malformed,
}
@ -761,6 +764,7 @@ fn validate_envelope(
pub struct CodexCallerAdmission {
caller_runtime_id: String,
connection_key_epoch: u32,
security: CodexTransportKey,
allowed_models: HashSet<String>,
max_concurrent_requests: usize,
@ -772,6 +776,7 @@ impl CodexCallerAdmission {
pub fn new(
caller_runtime_id: impl Into<String>,
connection_key: impl Into<String>,
connection_key_epoch: u32,
allowed_models: impl IntoIterator<Item = String>,
max_concurrent_requests: usize,
max_payload_bytes: usize,
@ -780,7 +785,8 @@ impl CodexCallerAdmission {
let caller_runtime_id = caller_runtime_id.into();
require_id(&caller_runtime_id, "caller_runtime_id")?;
let allowed_models = allowed_models.into_iter().collect::<HashSet<_>>();
if allowed_models.is_empty()
if connection_key_epoch == 0
|| allowed_models.is_empty()
|| allowed_models
.iter()
.any(|model| require_id(model, "allowed_model").is_err())
@ -794,6 +800,7 @@ impl CodexCallerAdmission {
let security = CodexTransportKey::from_connection_secret(connection_key.as_str())?;
Ok(Self {
caller_runtime_id,
connection_key_epoch,
security,
allowed_models,
max_concurrent_requests,
@ -813,17 +820,27 @@ struct RequestIdentity {
request_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ActiveRequest {
invocation_sha256: [u8; 32],
expires_at_unix_ms: u64,
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum CodexReplayState {
Active,
Completed { response: CodexTransportEnvelope },
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct CompletedRequest {
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "gamecult.codex.replay_record.v1")]
struct CodexReplayRecord {
#[cultcache(key = 0)]
caller_runtime_id: String,
#[cultcache(key = 1)]
request_id: String,
#[cultcache(key = 2)]
connection_key_epoch: u32,
#[cultcache(key = 3)]
invocation_sha256: [u8; 32],
#[cultcache(key = 4)]
expires_at_unix_ms: u64,
response: CodexTransportEnvelope,
#[cultcache(key = 5)]
state: CodexReplayState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -846,13 +863,14 @@ pub enum CodexTransportAdmission {
pub struct CodexTransportService {
callers: HashMap<String, CodexCallerAdmission>,
active: HashMap<RequestIdentity, ActiveRequest>,
completed: HashMap<RequestIdentity, CompletedRequest>,
live: HashSet<RequestIdentity>,
replay_store: CultCache,
max_expiry_skew_ms: u64,
}
impl CodexTransportService {
pub fn new(
pub fn open(
replay_store_path: &Path,
admissions: impl IntoIterator<Item = CodexCallerAdmission>,
max_expiry_skew_ms: u64,
) -> Result<Self, ServiceError> {
@ -873,10 +891,43 @@ impl CodexTransportService {
if callers.is_empty() {
return Err(ServiceError::InvalidAdmission);
}
let mut replay_store = CultCache::new();
replay_store
.register_entry_type::<CodexReplayRecord>()
.map_err(replay_store_error)?;
replay_store.add_generic_backing_store(
OwnedRedbMessagePackBackingStore::new(replay_store_path).map_err(replay_store_error)?,
);
replay_store
.pull_all_backing_stores()
.map_err(replay_store_error)?;
for (key, record) in replay_store
.get_all_with_keys::<CodexReplayRecord>()
.map_err(replay_store_error)?
{
let identity = RequestIdentity {
caller_runtime_id: record.caller_runtime_id.clone(),
request_id: record.request_id.clone(),
};
if key != replay_key(&identity)
|| record.invocation_sha256 == [0; 32]
|| record.expires_at_unix_ms == 0
{
return Err(ServiceError::InvalidReplayRecord);
}
let Some(caller) = callers.get(&record.caller_runtime_id) else {
continue;
};
if record.connection_key_epoch != caller.connection_key_epoch {
return Err(ServiceError::ReplayCallerKeyMismatch);
}
}
Ok(Self {
callers,
active: HashMap::new(),
completed: HashMap::new(),
live: HashSet::new(),
replay_store,
max_expiry_skew_ms,
})
}
@ -909,48 +960,55 @@ impl CodexTransportService {
return self.reply_refusal(&invocation, CodexRefusal::Policy, &security);
}
self.active
.retain(|_, request| request.expires_at_unix_ms >= now_unix_ms);
self.completed
.retain(|_, request| request.expires_at_unix_ms >= now_unix_ms);
let identity = RequestIdentity {
caller_runtime_id: invocation.caller_runtime_id.clone(),
request_id: invocation.request_id().to_string(),
};
let invocation_sha256: [u8; 32] =
Sha256::digest(canonical_invocation_bytes(&invocation)?).into();
if let Some(completed) = self.completed.get(&identity) {
return if completed.invocation_sha256 == invocation_sha256 {
Ok(CodexTransportAdmission::Reply(completed.response.clone()))
} else {
self.reply_refusal(&invocation, CodexRefusal::ReplayConflict, &security)
if let Some(record) = self
.replay_store
.get::<CodexReplayRecord>(&replay_key(&identity))
.map_err(replay_store_error)?
{
if record.invocation_sha256 != invocation_sha256 {
return self.reply_refusal(&invocation, CodexRefusal::ReplayConflict, &security);
}
return match record.state {
CodexReplayState::Completed { response } => {
Ok(CodexTransportAdmission::Reply(response))
}
CodexReplayState::Active => {
let refusal = if self.live.contains(&identity) {
CodexRefusal::InFlight
} else {
CodexRefusal::Indeterminate
};
self.reply_refusal(&invocation, refusal, &security)
}
};
}
if let Some(active) = self.active.get(&identity) {
let refusal = if active.invocation_sha256 == invocation_sha256 {
CodexRefusal::InFlight
} else {
CodexRefusal::ReplayConflict
};
return self.reply_refusal(&invocation, refusal, &security);
}
let active_for_caller = self
.active
.keys()
.live
.iter()
.filter(|key| key.caller_runtime_id == invocation.caller_runtime_id)
.count();
if active_for_caller >= max_concurrent_requests {
return self.reply_refusal(&invocation, CodexRefusal::Capacity, &security);
}
self.active.insert(
identity,
ActiveRequest {
invocation_sha256,
expires_at_unix_ms: invocation.expires_at_unix_ms,
},
);
let replay_record = CodexReplayRecord {
caller_runtime_id: identity.caller_runtime_id.clone(),
request_id: identity.request_id.clone(),
connection_key_epoch: caller.connection_key_epoch,
invocation_sha256,
expires_at_unix_ms: invocation.expires_at_unix_ms,
state: CodexReplayState::Active,
};
self.replay_store
.put(replay_key(&identity), &replay_record)
.map_err(replay_store_error)?;
self.live.insert(identity);
Ok(CodexTransportAdmission::Execute(Box::new(
CodexExecutionClaim {
invocation,
@ -975,11 +1033,17 @@ impl CodexTransportService {
caller_runtime_id: claim.invocation.caller_runtime_id.clone(),
request_id: claim.invocation.request_id().to_string(),
};
if !self.live.contains(&identity) {
return Err(ServiceError::NoActiveRequest);
}
let active = self
.active
.get(&identity)
.replay_store
.get::<CodexReplayRecord>(&replay_key(&identity))
.map_err(replay_store_error)?
.ok_or(ServiceError::NoActiveRequest)?;
if active.invocation_sha256 != claim.invocation_sha256 {
if active.invocation_sha256 != claim.invocation_sha256
|| !matches!(active.state, CodexReplayState::Active)
{
return Err(ServiceError::ActiveRequestMismatch);
}
let caller = self
@ -987,27 +1051,21 @@ impl CodexTransportService {
.get(&identity.caller_runtime_id)
.ok_or(ServiceError::CallerNotAdmitted)?;
let response = encrypt_result(&result, &caller.security)?;
self.active.remove(&identity);
self.completed.insert(
identity,
CompletedRequest {
invocation_sha256: claim.invocation_sha256,
expires_at_unix_ms: claim.invocation.expires_at_unix_ms,
let replay_record = CodexReplayRecord {
caller_runtime_id: identity.caller_runtime_id.clone(),
request_id: identity.request_id.clone(),
connection_key_epoch: caller.connection_key_epoch,
invocation_sha256: claim.invocation_sha256,
expires_at_unix_ms: claim.invocation.expires_at_unix_ms,
state: CodexReplayState::Completed {
response: response.clone(),
},
);
Ok(response)
}
pub fn cancel(&mut self, claim: &CodexExecutionClaim) -> bool {
let identity = RequestIdentity {
caller_runtime_id: claim.invocation.caller_runtime_id.clone(),
request_id: claim.invocation.request_id().to_string(),
};
self.active
.get(&identity)
.is_some_and(|active| active.invocation_sha256 == claim.invocation_sha256)
&& self.active.remove(&identity).is_some()
self.replay_store
.put(replay_key(&identity), &replay_record)
.map_err(replay_store_error)?;
self.live.remove(&identity);
Ok(response)
}
fn reply_refusal(
@ -1023,6 +1081,26 @@ impl CodexTransportService {
}
}
fn replay_key(identity: &RequestIdentity) -> String {
let mut hasher = Sha256::new();
hasher.update((identity.caller_runtime_id.len() as u64).to_be_bytes());
hasher.update(identity.caller_runtime_id.as_bytes());
hasher.update((identity.request_id.len() as u64).to_be_bytes());
hasher.update(identity.request_id.as_bytes());
const HEX: &[u8; 16] = b"0123456789abcdef";
let digest = hasher.finalize();
let mut key = String::with_capacity(64);
for byte in digest {
key.push(HEX[(byte >> 4) as usize] as char);
key.push(HEX[(byte & 0x0f) as usize] as char);
}
key
}
fn replay_store_error(error: impl std::fmt::Display) -> ServiceError {
ServiceError::ReplayStore(error.to_string())
}
fn contract_refusal(error: &ContractError) -> CodexRefusal {
match error {
ContractError::Expiry => CodexRefusal::Expired,
@ -1042,6 +1120,12 @@ pub enum ServiceError {
DuplicateCaller,
#[error("callers must not share transport keys")]
SharedCallerKey,
#[error("connector replay store failed: {0}")]
ReplayStore(String),
#[error("connector replay record is malformed")]
InvalidReplayRecord,
#[error("caller key changed while durable replay records still exist")]
ReplayCallerKeyMismatch,
#[error("caller runtime is not admitted")]
CallerNotAdmitted,
#[error("transport payload exceeds the caller bound")]
@ -1130,6 +1214,33 @@ pub enum ContractError {
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
static TEST_STORE_SEQUENCE: AtomicUsize = AtomicUsize::new(0);
struct TestStore {
root: std::path::PathBuf,
path: std::path::PathBuf,
}
impl TestStore {
fn new() -> Self {
let sequence = TEST_STORE_SEQUENCE.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"codex-connector-replay-{}-{sequence}",
std::process::id()
));
std::fs::create_dir_all(&root).unwrap();
let path = root.join("replay.cc");
Self { root, path }
}
}
impl Drop for TestStore {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.root);
}
}
fn request() -> CodexProviderRequest {
let mut request = CodexProviderRequest::new(
@ -1330,6 +1441,7 @@ mod tests {
CodexCallerAdmission::new(
caller_runtime_id,
key,
1,
["gpt-5.4".to_string()],
max_concurrent_requests,
64 * 1024,
@ -1338,8 +1450,9 @@ mod tests {
.unwrap()
}
fn service(max_concurrent_requests: usize) -> CodexTransportService {
CodexTransportService::new(
fn open_service(store: &TestStore, max_concurrent_requests: usize) -> CodexTransportService {
CodexTransportService::open(
&store.path,
[
caller(
"epiphany-yggdrasil",
@ -1397,8 +1510,10 @@ mod tests {
#[test]
fn service_refuses_shared_keys_and_isolates_caller_request_identities() {
let store = TestStore::new();
assert!(matches!(
CodexTransportService::new(
CodexTransportService::open(
&store.path,
[
caller("epiphany-yggdrasil", "shared-key", 1),
caller("ghostlight-yggdrasil", "shared-key", 1),
@ -1410,7 +1525,7 @@ mod tests {
let epiphany = invocation_for("epiphany-yggdrasil", "shared-request-id");
let ghostlight = invocation_for("ghostlight-yggdrasil", "shared-request-id");
let mut service = service(1);
let mut service = open_service(&store, 1);
assert!(matches!(
service
.begin(
@ -1435,9 +1550,10 @@ mod tests {
#[test]
fn service_returns_exact_completed_replay_and_refuses_conflicting_replay() {
let store = TestStore::new();
let invocation = invocation();
let security = security("epiphany-distinct-test-key");
let mut service = service(1);
let mut service = open_service(&store, 1);
let claim = match service
.begin(&encrypt_invocation(&invocation, &security).unwrap(), 1_000)
.unwrap()
@ -1462,6 +1578,8 @@ mod tests {
),
)
.unwrap();
drop(service);
let mut service = open_service(&store, 1);
let replay = match service
.begin(&encrypt_invocation(&invocation, &security).unwrap(), 1_000)
@ -1499,10 +1617,11 @@ mod tests {
#[test]
fn service_refuses_duplicate_inflight_and_per_caller_capacity() {
let store = TestStore::new();
let first = invocation_for("epiphany-yggdrasil", "request-1");
let second = invocation_for("epiphany-yggdrasil", "request-2");
let security = security("epiphany-distinct-test-key");
let mut service = service(1);
let mut service = open_service(&store, 1);
let first_envelope = encrypt_invocation(&first, &security).unwrap();
assert!(matches!(
service.begin(&first_envelope, 1_000).unwrap(),
@ -1528,4 +1647,59 @@ mod tests {
);
}
}
#[test]
fn restart_refuses_ambiguous_claim_without_consuming_live_capacity() {
let store = TestStore::new();
let first = invocation_for("epiphany-yggdrasil", "request-1");
let second = invocation_for("epiphany-yggdrasil", "request-2");
let security = security("epiphany-distinct-test-key");
let mut service = open_service(&store, 1);
assert!(matches!(
service
.begin(&encrypt_invocation(&first, &security).unwrap(), 1_000)
.unwrap(),
CodexTransportAdmission::Execute(_)
));
drop(service);
let mut restarted = open_service(&store, 1);
let ambiguous = match restarted
.begin(&encrypt_invocation(&first, &security).unwrap(), 1_000)
.unwrap()
{
CodexTransportAdmission::Reply(response) => response,
CodexTransportAdmission::Execute(_) => panic!("ambiguous claim re-executed"),
};
assert_eq!(
decrypt_result(&ambiguous, &security, &first)
.unwrap()
.disposition,
CodexTransportDisposition::Refused(CodexRefusal::Indeterminate)
);
assert!(matches!(
restarted
.begin(&encrypt_invocation(&second, &security).unwrap(), 1_000)
.unwrap(),
CodexTransportAdmission::Execute(_)
));
drop(restarted);
assert!(matches!(
CodexTransportService::open(
&store.path,
[CodexCallerAdmission::new(
"epiphany-yggdrasil",
"rotated-connection-key",
2,
["gpt-5.4".to_string()],
1,
64 * 1024,
32_768,
)
.unwrap()],
5_000,
),
Err(ServiceError::ReplayCallerKeyMismatch)
));
}
}