Merge pull request 'Pin CultLib 75188fde (stamp hardening #175)' (#66) from hands/cultlib-pin-75188fde into main

This commit is contained in:
Eureka 2026-11-14 09:35:41 +00:00
commit 64e88966ad
9 changed files with 99 additions and 149 deletions

167
Cargo.lock generated
View file

@ -37,17 +37,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "ahash"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
"getrandom 0.2.17",
"once_cell",
"version_check",
]
[[package]]
name = "ahash"
version = "0.8.12"
@ -195,12 +184,6 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.2"
@ -369,18 +352,21 @@ dependencies = [
[[package]]
name = "cultcache-rs"
version = "0.2.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2#8fc74c709444b884a2bd339e0c306bf9484d99d2"
version = "0.3.1"
source = "git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6#75188fdefa0b4baf131dbfd94e77d06a965f0cc6"
dependencies = [
"anyhow",
"chrono",
"cultcache-rs-derive 0.1.0",
"cultcache-rs-derive 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6)",
"fs2",
"redb",
"rmp-serde",
"serde",
"serde_bytes",
"serde_json",
"serde_stacker",
"sha2",
"stacker",
"uuid",
"windows-sys 0.59.0",
]
@ -392,7 +378,7 @@ source = "git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f111
dependencies = [
"anyhow",
"chrono",
"cultcache-rs-derive 0.2.0",
"cultcache-rs-derive 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23)",
"fs2",
"redb",
"rmp-serde",
@ -407,8 +393,8 @@ dependencies = [
[[package]]
name = "cultcache-rs-derive"
version = "0.1.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2#8fc74c709444b884a2bd339e0c306bf9484d99d2"
version = "0.2.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6#75188fdefa0b4baf131dbfd94e77d06a965f0cc6"
dependencies = [
"proc-macro2",
"quote",
@ -427,18 +413,17 @@ dependencies = [
[[package]]
name = "cultnet-rs"
version = "0.1.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2#8fc74c709444b884a2bd339e0c306bf9484d99d2"
version = "0.2.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23#a4eae9056152335662f1115f97bf35fb00ab6d23"
dependencies = [
"aes-gcm",
"anyhow",
"base64",
"chrono",
"cultcache-rs 0.2.0",
"cultcache-rs 0.3.1 (git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23)",
"ed25519-dalek",
"hmac",
"rand",
"reed-solomon-erasure",
"rmp-serde",
"rmpv",
"serde",
@ -453,16 +438,18 @@ dependencies = [
[[package]]
name = "cultnet-rs"
version = "0.2.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23#a4eae9056152335662f1115f97bf35fb00ab6d23"
version = "0.3.0"
source = "git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6#75188fdefa0b4baf131dbfd94e77d06a965f0cc6"
dependencies = [
"aes-gcm",
"anyhow",
"base64",
"chrono",
"cultcache-rs 0.3.1",
"cultcache-rs 0.3.1 (git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6)",
"ed25519-dalek",
"fs2",
"hmac",
"libc",
"rand",
"rmp-serde",
"rmpv",
@ -625,7 +612,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"cultcache-rs 0.2.0",
"cultcache-rs 0.3.1 (git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6)",
"rmp-serde",
"schemars",
"serde",
@ -639,7 +626,7 @@ dependencies = [
"anyhow",
"chrono",
"clap",
"cultnet-rs 0.1.0",
"cultnet-rs 0.3.0",
"huginn-daemon",
"huginn-mind",
"rmcp",
@ -874,15 +861,6 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.8",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
@ -932,7 +910,7 @@ dependencies = [
"anyhow",
"base64",
"chrono",
"cultnet-rs 0.1.0",
"cultnet-rs 0.3.0",
"huginn-mind",
"rmp-serde",
"rustix",
@ -951,8 +929,8 @@ dependencies = [
"anyhow",
"base64",
"chrono",
"cultcache-rs 0.2.0",
"cultnet-rs 0.1.0",
"cultcache-rs 0.3.1 (git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6)",
"cultnet-rs 0.3.0",
"eureka-pipeline",
"jsonschema",
"rmp-serde",
@ -1004,15 +982,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@ -1042,7 +1011,7 @@ version = "0.58.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f622c574a29fb3034a0a90645afee194dfedc75fc124228801b6e77041075bd1"
dependencies = [
"ahash 0.8.12",
"ahash",
"bytecount",
"data-encoding",
"email_address",
@ -1079,7 +1048,7 @@ version = "0.58.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39b7670345e5ce87371339d13705dbb9276b7e3e934bbe1efa2c6734a1f65a96"
dependencies = [
"ahash 0.8.12",
"ahash",
"bytecount",
"fraction",
"getrandom 0.3.4",
@ -1095,12 +1064,6 @@ version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@ -1122,15 +1085,6 @@ version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "lru"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "memchr"
version = "2.8.3"
@ -1150,8 +1104,8 @@ dependencies = [
"anyhow",
"base64",
"chrono",
"cultcache-rs 0.2.0",
"cultnet-rs 0.1.0",
"cultcache-rs 0.3.1 (git+https://github.com/GameCult/CultLib.git?rev=75188fdefa0b4baf131dbfd94e77d06a965f0cc6)",
"cultnet-rs 0.3.0",
"eureka-pipeline",
"eureka-state",
"ghostlight-persona-projection",
@ -1179,7 +1133,7 @@ source = "git+https://eureka.gamecult.org/forge/GameCult/ModelConnector.git?rev=
dependencies = [
"aes-gcm",
"base64",
"cultcache-rs 0.3.1",
"cultcache-rs 0.3.1 (git+https://github.com/GameCult/CultLib.git?rev=a4eae9056152335662f1115f97bf35fb00ab6d23)",
"cultnet-rs 0.2.0",
"rand_core 0.6.4",
"rmp-serde",
@ -1304,17 +1258,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
@ -1322,21 +1265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core 0.9.12",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
"parking_lot_core",
]
[[package]]
@ -1347,7 +1276,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.18",
"redox_syscall",
"smallvec",
"windows-link",
]
@ -1488,35 +1417,13 @@ dependencies = [
"libc",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.13.2",
]
[[package]]
name = "reed-solomon-erasure"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7263373d500d4d4f505d43a2a662d475a894aa94503a1ee28e9188b5f3960d4f"
dependencies = [
"libm",
"lru",
"parking_lot 0.11.2",
"smallvec",
"spin",
"bitflags",
]
[[package]]
@ -1545,13 +1452,13 @@ version = "0.58.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558f3855529c0a7e78a48e409fa653456e55f1b167eab4109c16a255b7727e3f"
dependencies = [
"ahash 0.8.12",
"ahash",
"fluent-uri",
"getrandom 0.3.4",
"hashbrown 0.17.1",
"hashbrown",
"itoa",
"micromap",
"parking_lot 0.12.5",
"parking_lot",
"percent-encoding",
"serde_json",
]
@ -1664,7 +1571,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.13.2",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
@ -1875,12 +1782,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
[[package]]
name = "spki"
version = "0.7.3"

View file

@ -16,5 +16,5 @@ publish = false
# The one CultLib revision the workspace speaks. Every crate inherits these
# entries; a bump is a change to this table and nowhere else.
[workspace.dependencies]
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultcache-rs" }
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultnet-rs" }
cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "75188fdefa0b4baf131dbfd94e77d06a965f0cc6", package = "cultcache-rs" }
cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "75188fdefa0b4baf131dbfd94e77d06a965f0cc6", package = "cultnet-rs" }

View file

@ -330,13 +330,13 @@ impl CacheBackingStore for LandsThenFails {
fn pull_all(&self) -> Result<Vec<CultCacheEnvelope>> {
self.inner.pull_all()
}
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<String> {
self.inner.push(entry)
}
fn delete(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
self.inner.delete(entry)
}
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> Result<()> {
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> Result<Vec<String>> {
self.inner.push_all(entries, options)
}
}
@ -547,13 +547,13 @@ impl CacheBackingStore for FailsTheLook {
}
self.inner.pull_all()
}
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<String> {
self.inner.push(entry)
}
fn delete(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
self.inner.delete(entry)
}
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> Result<()> {
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> Result<Vec<String>> {
self.inner.push_all(entries, options)
}
}

View file

@ -374,7 +374,7 @@ mod tests {
self.inner.pull_all()
}
fn push(&mut self, entry: &CultCacheEnvelope) -> anyhow::Result<()> {
fn push(&mut self, entry: &CultCacheEnvelope) -> anyhow::Result<String> {
self.inner.push(entry)
}
@ -382,7 +382,7 @@ mod tests {
self.inner.delete(entry)
}
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> anyhow::Result<()> {
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> anyhow::Result<Vec<String>> {
self.inner.push_all(entries, options)
}
}

View file

@ -116,9 +116,9 @@ pub(crate) mod test_stores {
Ok(self.rows())
}
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<String> {
self.plant(entry.clone());
Ok(())
Ok(entry.stored_at.clone())
}
fn delete(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
@ -126,9 +126,9 @@ pub(crate) mod test_stores {
Ok(())
}
fn push_all(&mut self, entries: &[CultCacheEnvelope], _options: PushAllOptions) -> Result<()> {
fn push_all(&mut self, entries: &[CultCacheEnvelope], _options: PushAllOptions) -> Result<Vec<String>> {
*self.rows.lock().unwrap() = entries.iter().map(|entry| (identity(entry), entry.clone())).collect();
Ok(())
Ok(entries.iter().map(|entry| entry.stored_at.clone()).collect())
}
}
@ -199,7 +199,7 @@ pub(crate) mod test_stores {
self.inner.pull_all()
}
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<()> {
fn push(&mut self, entry: &CultCacheEnvelope) -> Result<String> {
self.inner.push(entry)
}
@ -207,7 +207,7 @@ pub(crate) mod test_stores {
self.inner.delete(entry)
}
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> Result<()> {
fn push_all(&mut self, entries: &[CultCacheEnvelope], options: PushAllOptions) -> Result<Vec<String>> {
self.inner.push_all(entries, options)
}
}

View file

@ -36,7 +36,7 @@
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use anyhow::{Context, Result, bail};
use chrono::{DateTime, Utc};
use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry, SingleFileMessagePackBackingStore};
use eureka_pipeline::Slug;
@ -414,6 +414,10 @@ impl ControlWriter {
let mut cache = registered_cache()?;
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path))?;
cache.pull_all_backing_stores()?;
// A pull carries a record no registered type claims instead of refusing it; the writer still must not write beside one.
if !cache.foreign_records().is_empty() {
bail!("{} holds a record this control store does not read: archive it, nothing migrates it", path.display());
}
Ok(Self { cache, path })
}
@ -502,6 +506,10 @@ impl SwarmWriter {
let mut cache = swarm_cache()?;
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path))?;
cache.pull_all_backing_stores()?;
// A pull carries a record no registered type claims instead of refusing it; the writer still must not write beside one.
if !cache.foreign_records().is_empty() {
bail!("{} holds a record this swarm store does not read: archive it, nothing migrates it", path.display());
}
Ok(Self { cache, path })
}

View file

@ -62,6 +62,7 @@ fn a_brake_payload_that_does_not_decode_makes_the_store_undecodable() {
let mut cache = CultCache::new();
cache.register_entry_type::<WrongBrake>().unwrap();
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path)).unwrap();
cache.pull_all_backing_stores().unwrap();
cache.put(BRAKE_KEY, &WrongBrake { released: "yes".into() }).unwrap();
assert_eq!(effective(&path), held(HeldReason::Undecodable));
}
@ -254,3 +255,34 @@ fn release(root: &Path, name: &str) {
ControlWriter::open(root.join(name).join("control.cc")).unwrap().set_brake(true, at(0), "op").unwrap();
release_swarm(root);
}
#[test]
fn a_writer_refuses_to_open_over_a_store_holding_a_record_it_does_not_read() {
let dir = tempfile::tempdir().unwrap();
let path = store_path(dir.path());
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let mut cache = CultCache::new();
cache.register_entry_type::<OldDial>().unwrap();
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path)).unwrap();
cache.pull_all_backing_stores().unwrap();
cache.put("dial", &OldDial { heat: "1".into() }).unwrap();
let before = std::fs::read(&path).unwrap();
let refusal = ControlWriter::open(&path).err().expect("a foreign record refuses the open").to_string();
assert!(refusal.contains("does not read"), "{refusal}");
assert_eq!(std::fs::read(&path).unwrap(), before, "the refused open leaves the store bytes alone");
}
#[test]
fn a_swarm_writer_refuses_to_open_over_a_store_holding_a_record_it_does_not_read() {
let dir = tempfile::tempdir().unwrap();
let path = swarm_path(dir.path());
let mut cache = CultCache::new();
cache.register_entry_type::<OldDial>().unwrap();
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path)).unwrap();
cache.pull_all_backing_stores().unwrap();
cache.put("dial", &OldDial { heat: "1".into() }).unwrap();
let before = std::fs::read(&path).unwrap();
let refusal = SwarmWriter::open(&path).err().expect("a foreign record refuses the open").to_string();
assert!(refusal.contains("does not read"), "{refusal}");
assert_eq!(std::fs::read(&path).unwrap(), before, "the refused open leaves the store bytes alone");
}

View file

@ -309,6 +309,10 @@ impl FileStore {
let mut cache = Self::registered()?;
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&self.path))?;
cache.pull_all_backing_stores().with_context(|| self.refusal())?;
// A pull now carries a record no registered type claims instead of refusing it; the writer still must not write beside one.
if !cache.foreign_records().is_empty() {
return Err(anyhow!(self.refusal()));
}
Ok(cache)
}

View file

@ -187,7 +187,12 @@ mod unsafe_installs {
std::fs::create_dir(&instance).unwrap();
for (name, mode) in [("control.cc.lock", lock_mode), ("control.cc", store_mode)] {
if let Some(mode) = mode {
std::fs::write(instance.join(name), b"").unwrap();
if name == "control.cc" {
// A store is a store the runtime reads: CultCache refuses a zero-byte file, as it refuses any file it cannot read.
ControlWriter::open(instance.join(name)).unwrap().set_brake(false, at(0), "fixture").unwrap();
} else {
std::fs::write(instance.join(name), b"").unwrap();
}
chmod(&instance.join(name), mode);
}
}