memoryd: the crate, memoryd.toml, and listening on a socket
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
Generated
+204
-1
@@ -18,6 +18,12 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bxctl"
|
name = "bxctl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -70,12 +76,30 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
|
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gatewayd"
|
name = "gatewayd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -100,11 +124,32 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humantime"
|
name = "humantime"
|
||||||
@@ -119,7 +164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.17.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -135,12 +180,33 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.105"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.189"
|
version = "0.2.189"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.38.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "loopd"
|
name = "loopd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -157,6 +223,17 @@ version = "2.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoryd"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proto",
|
||||||
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -169,6 +246,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.107"
|
version = "1.0.107"
|
||||||
@@ -212,6 +295,31 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rsqlite-vfs"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.16.1",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.40.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
"sqlite-wasm-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.45"
|
version = "0.23.45"
|
||||||
@@ -258,6 +366,12 @@ dependencies = [
|
|||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.29"
|
version = "0.1.29"
|
||||||
@@ -348,6 +462,24 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sqlite-wasm-rs"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"js-sys",
|
||||||
|
"rsqlite-vfs",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@@ -365,6 +497,26 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "1.1.6+spec-1.1.0"
|
version = "1.1.6+spec-1.1.0"
|
||||||
@@ -425,12 +577,63 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.1+wasi-snapshot-preview1"
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.128"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.128"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.128"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.128"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ members = [
|
|||||||
"crates/loopd",
|
"crates/loopd",
|
||||||
"crates/brokerd",
|
"crates/brokerd",
|
||||||
"crates/gatewayd",
|
"crates/gatewayd",
|
||||||
|
"crates/memoryd",
|
||||||
"crates/inferproxy",
|
"crates/inferproxy",
|
||||||
"crates/toolkit",
|
"crates/toolkit",
|
||||||
"crates/bxctl",
|
"crates/bxctl",
|
||||||
@@ -28,4 +29,5 @@ emsha = "1.0.4"
|
|||||||
rustls = { version = "0.23.45", default-features = false, features = ["ring", "std", "tls12"] }
|
rustls = { version = "0.23.45", default-features = false, features = ["ring", "std", "tls12"] }
|
||||||
rustls-native-certs = "0.8.4"
|
rustls-native-certs = "0.8.4"
|
||||||
zeroize = "1.9.0"
|
zeroize = "1.9.0"
|
||||||
|
rusqlite = { version = "0.40.2", features = ["bundled"] }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "memoryd"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proto.workspace = true
|
||||||
|
rusqlite.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
toml.workspace = true
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
//! `memoryd` configuration: read `memoryd.toml` into a typed `Config` (M5a spec, section 11).
|
||||||
|
//!
|
||||||
|
//! Unknown keys are errors in every table, as in the other roles: a misspelt key that silently
|
||||||
|
//! fell back to its default would be a setting the owner believes is set and is not.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use proto::DataClass;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct Paths {
|
||||||
|
pub home: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Paths {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
home: std::env::var_os("BOXMAKER_HOME")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/var/lib/boxmaker")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `[memory]`: the starter `soul.md`, and whether identity writes wait for the owner.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct Memory {
|
||||||
|
/// A relative path is resolved next to the config file by `Config::load`.
|
||||||
|
pub soul_starter: PathBuf,
|
||||||
|
pub review: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Memory {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
soul_starter: PathBuf::from("soul.md"),
|
||||||
|
review: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `[index]`: records above `max_class` are not indexed.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct Index {
|
||||||
|
pub max_class: DataClass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Index {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
max_class: DataClass::Private,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An empty path means "the default under `home`".
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct Sockets {
|
||||||
|
#[serde(rename = "loop")]
|
||||||
|
pub loopd: PathBuf,
|
||||||
|
pub broker: PathBuf,
|
||||||
|
pub admin: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
|
||||||
|
#[serde(deny_unknown_fields, default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub paths: Paths,
|
||||||
|
pub memory: Memory,
|
||||||
|
pub index: Index,
|
||||||
|
pub sockets: Sockets,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
Read(PathBuf, std::io::Error),
|
||||||
|
Parse(PathBuf, toml::de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ConfigError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConfigError::Read(path, err) => write!(f, "{}: {err}", path.display()),
|
||||||
|
ConfigError::Parse(path, err) => write!(f, "{}: {err}", path.display()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ConfigError {}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn parse(text: &str) -> Result<Config, toml::de::Error> {
|
||||||
|
toml::from_str(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read and parse `path`; a relative `soul_starter` is taken to be next to it.
|
||||||
|
pub fn load(path: &Path) -> Result<Config, ConfigError> {
|
||||||
|
let text =
|
||||||
|
std::fs::read_to_string(path).map_err(|e| ConfigError::Read(path.to_path_buf(), e))?;
|
||||||
|
let mut config =
|
||||||
|
Config::parse(&text).map_err(|e| ConfigError::Parse(path.to_path_buf(), e))?;
|
||||||
|
if config.memory.soul_starter.is_relative() {
|
||||||
|
let base = path.parent().unwrap_or_else(|| Path::new("."));
|
||||||
|
config.memory.soul_starter = base.join(&config.memory.soul_starter);
|
||||||
|
}
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn socket(&self, set: &Path, default: &str) -> PathBuf {
|
||||||
|
if set.as_os_str().is_empty() {
|
||||||
|
self.paths.home.join(default)
|
||||||
|
} else {
|
||||||
|
set.to_path_buf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loop_socket(&self) -> PathBuf {
|
||||||
|
self.socket(&self.sockets.loopd, "run/loop-memory/memory.sock")
|
||||||
|
}
|
||||||
|
pub fn broker_socket(&self) -> PathBuf {
|
||||||
|
self.socket(&self.sockets.broker, "run/broker-memory/memory.sock")
|
||||||
|
}
|
||||||
|
pub fn admin_socket(&self) -> PathBuf {
|
||||||
|
self.socket(&self.sockets.admin, "run/owner-memory/admin.sock")
|
||||||
|
}
|
||||||
|
pub fn memory_dir(&self) -> PathBuf {
|
||||||
|
self.paths.home.join("memory")
|
||||||
|
}
|
||||||
|
pub fn index_path(&self) -> PathBuf {
|
||||||
|
self.paths.home.join("index/memory.sqlite3")
|
||||||
|
}
|
||||||
|
pub fn sessions_dir(&self) -> PathBuf {
|
||||||
|
self.paths.home.join("sessions")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//! The memory role: the memory files, their index, and every memory tool call (M5a spec).
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod socket;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//! `memoryd serve --config <path>`.
|
||||||
|
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
const USAGE: &str = "usage: memoryd serve --config <path>";
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
let args: Vec<OsString> = std::env::args_os().skip(1).collect();
|
||||||
|
let Some(config) = parse(&args) else {
|
||||||
|
eprintln!("{USAGE}");
|
||||||
|
return ExitCode::from(2);
|
||||||
|
};
|
||||||
|
match memoryd::config::Config::load(&config) {
|
||||||
|
Ok(_) => {
|
||||||
|
eprintln!("memoryd: not implemented");
|
||||||
|
ExitCode::from(2)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("memoryd: {e}\nsee docs/runbook.md#memoryd-start-failed");
|
||||||
|
ExitCode::from(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The config path of `serve --config <path>`, or `None` for any other list.
|
||||||
|
fn parse(args: &[OsString]) -> Option<PathBuf> {
|
||||||
|
match args {
|
||||||
|
[serve, flag, path] if serve == "serve" && flag == "--config" => Some(PathBuf::from(path)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
//! Listening on a Unix socket: the directory at 0700, a stale socket removed, the socket at 0600
|
||||||
|
//! (M5a spec, section 7). The same steps as `brokerd`'s; no crate depends on another role's.
|
||||||
|
|
||||||
|
use std::fs::{DirBuilder, Permissions};
|
||||||
|
use std::io;
|
||||||
|
use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
|
||||||
|
use std::os::unix::net::UnixListener;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Bind one socket: prepare its directory at 0700, make it private, remove any stale socket,
|
||||||
|
/// bind, then make the socket itself private at 0600. The error names the path.
|
||||||
|
pub fn listen(socket: &Path) -> Result<UnixListener, String> {
|
||||||
|
// A socket needs a directory of its own: step 2 makes that directory 0700, which must never
|
||||||
|
// be `/` or a shared directory reached through a symbolic link.
|
||||||
|
let dir = match socket.parent() {
|
||||||
|
Some(parent) if parent.parent().is_some() && !parent.as_os_str().is_empty() => {
|
||||||
|
parent.to_path_buf()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: a socket needs a directory of its own, not / or none",
|
||||||
|
socket.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. The directory, at 0700.
|
||||||
|
DirBuilder::new()
|
||||||
|
.recursive(true)
|
||||||
|
.mode(0o700)
|
||||||
|
.create(&dir)
|
||||||
|
.map_err(|e| format!("{}: {e}", dir.display()))?;
|
||||||
|
// `set_permissions` follows a symbolic link, so a link would make the directory it points to
|
||||||
|
// private instead: refuse it.
|
||||||
|
let kind = std::fs::symlink_metadata(&dir)
|
||||||
|
.map_err(|e| format!("{}: {e}", dir.display()))?
|
||||||
|
.file_type();
|
||||||
|
if !kind.is_dir() {
|
||||||
|
return Err(format!(
|
||||||
|
"{}: is a symbolic link or not a directory; memoryd will not change its mode",
|
||||||
|
dir.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// 2. Make it private, always, even when it was already there at 0755.
|
||||||
|
std::fs::set_permissions(&dir, Permissions::from_mode(0o700))
|
||||||
|
.map_err(|e| format!("{}: {e}", dir.display()))?;
|
||||||
|
|
||||||
|
// 3. A stale socket is removed; a missing one is fine.
|
||||||
|
match std::fs::remove_file(socket) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
|
||||||
|
Err(e) => return Err(format!("{}: {e}", socket.display())),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Bind, then 5. make the socket private.
|
||||||
|
let listener = UnixListener::bind(socket).map_err(|e| format!("{}: {e}", socket.display()))?;
|
||||||
|
std::fs::set_permissions(socket, Permissions::from_mode(0o600))
|
||||||
|
.map_err(|e| format!("{}: {e}", socket.display()))?;
|
||||||
|
Ok(listener)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
//! `memoryd.toml`: defaults, paths under `home`, the starter next to the config file, and unknown
|
||||||
|
//! keys refused (M5a spec, section 11). Do not edit.
|
||||||
|
|
||||||
|
#[path = "support/tmp.rs"]
|
||||||
|
mod tmp;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use memoryd::config::Config;
|
||||||
|
use proto::DataClass;
|
||||||
|
use tmp::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaults_and_paths_under_home() {
|
||||||
|
let config = Config::parse("[paths]\nhome = \"/srv/bx\"\n").unwrap();
|
||||||
|
assert!(!config.memory.review);
|
||||||
|
assert_eq!(config.index.max_class, DataClass::Private);
|
||||||
|
assert_eq!(
|
||||||
|
config.loop_socket(),
|
||||||
|
PathBuf::from("/srv/bx/run/loop-memory/memory.sock")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.broker_socket(),
|
||||||
|
PathBuf::from("/srv/bx/run/broker-memory/memory.sock")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.admin_socket(),
|
||||||
|
PathBuf::from("/srv/bx/run/owner-memory/admin.sock")
|
||||||
|
);
|
||||||
|
assert_eq!(config.memory_dir(), PathBuf::from("/srv/bx/memory"));
|
||||||
|
assert_eq!(
|
||||||
|
config.index_path(),
|
||||||
|
PathBuf::from("/srv/bx/index/memory.sqlite3")
|
||||||
|
);
|
||||||
|
assert_eq!(config.sessions_dir(), PathBuf::from("/srv/bx/sessions"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_setting_can_be_given() {
|
||||||
|
let text = "[paths]\nhome = \"/h\"\n[memory]\nsoul_starter = \"/etc/bx/soul.md\"\nreview = true\n[index]\nmax_class = \"secret\"\n[sockets]\nloop = \"/r/l.sock\"\nbroker = \"/r/b.sock\"\nadmin = \"/r/a.sock\"\n";
|
||||||
|
let config = Config::parse(text).unwrap();
|
||||||
|
assert!(config.memory.review);
|
||||||
|
assert_eq!(config.index.max_class, DataClass::Secret);
|
||||||
|
assert_eq!(config.memory.soul_starter, PathBuf::from("/etc/bx/soul.md"));
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
config.loop_socket(),
|
||||||
|
config.broker_socket(),
|
||||||
|
config.admin_socket()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PathBuf::from("/r/l.sock"),
|
||||||
|
PathBuf::from("/r/b.sock"),
|
||||||
|
PathBuf::from("/r/a.sock")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unknown_keys_are_refused_in_every_table() {
|
||||||
|
for text in [
|
||||||
|
"[paths]\nhomee = \"/h\"\n",
|
||||||
|
"[memory]\nreveiw = true\n",
|
||||||
|
"[index]\nmax = \"private\"\n",
|
||||||
|
"[sockets]\nloopd = \"/x\"\n",
|
||||||
|
"[extra]\n",
|
||||||
|
"[index]\nmax_class = \"top-secret\"\n",
|
||||||
|
] {
|
||||||
|
assert!(Config::parse(text).is_err(), "{text}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_relative_starter_is_next_to_the_config_file() {
|
||||||
|
let dir = TempDir::new("config");
|
||||||
|
let path = dir.write("etc/memoryd.toml", "[memory]\nsoul_starter = \"soul.md\"\n");
|
||||||
|
let config = Config::load(&path).unwrap();
|
||||||
|
assert_eq!(config.memory.soul_starter, dir.path().join("etc/soul.md"));
|
||||||
|
let path = dir.write("etc/default.toml", "");
|
||||||
|
let config = Config::load(&path).unwrap();
|
||||||
|
assert_eq!(config.memory.soul_starter, dir.path().join("etc/soul.md"));
|
||||||
|
let path = dir.write(
|
||||||
|
"etc/abs.toml",
|
||||||
|
"[memory]\nsoul_starter = \"/opt/soul.md\"\n",
|
||||||
|
);
|
||||||
|
let config = Config::load(&path).unwrap();
|
||||||
|
assert_eq!(config.memory.soul_starter, PathBuf::from("/opt/soul.md"));
|
||||||
|
let missing = Config::load(&dir.path().join("nope.toml")).unwrap_err();
|
||||||
|
assert!(missing.to_string().contains("nope.toml"), "{missing}");
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//! `listen`: the directory at 0700, the socket at 0600, a stale socket replaced, and no mode
|
||||||
|
//! changed through a symbolic link (M5a spec, section 7). Do not edit.
|
||||||
|
|
||||||
|
#[path = "support/tmp.rs"]
|
||||||
|
mod tmp;
|
||||||
|
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
|
use memoryd::socket::listen;
|
||||||
|
use tmp::TempDir;
|
||||||
|
|
||||||
|
fn mode(path: &std::path::Path) -> u32 {
|
||||||
|
std::fs::metadata(path).unwrap().permissions().mode() & 0o777
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directory_and_socket_are_private_and_a_stale_socket_is_replaced() {
|
||||||
|
let dir = TempDir::new("socket");
|
||||||
|
let run = dir.path().join("run/loop-memory");
|
||||||
|
std::fs::create_dir_all(&run).unwrap();
|
||||||
|
std::fs::set_permissions(&run, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||||
|
let socket = run.join("memory.sock");
|
||||||
|
let first = listen(&socket).unwrap();
|
||||||
|
assert_eq!((mode(&run), mode(&socket)), (0o700, 0o600));
|
||||||
|
drop(first);
|
||||||
|
let second = listen(&socket).expect("a stale socket is removed");
|
||||||
|
assert!(UnixStream::connect(&socket).is_ok());
|
||||||
|
drop(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_directory_and_symbolic_links_are_refused() {
|
||||||
|
let dir = TempDir::new("socket-link");
|
||||||
|
let target = dir.path().join("shared");
|
||||||
|
std::fs::create_dir_all(&target).unwrap();
|
||||||
|
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||||
|
std::os::unix::fs::symlink(&target, dir.path().join("link")).unwrap();
|
||||||
|
let err = listen(&dir.path().join("link/memory.sock")).unwrap_err();
|
||||||
|
assert!(err.contains("symbolic link"), "{err}");
|
||||||
|
assert_eq!(mode(&target), 0o755, "the target's mode is untouched");
|
||||||
|
let err = listen(std::path::Path::new("/memory.sock")).unwrap_err();
|
||||||
|
assert!(err.contains("directory of its own"), "{err}");
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//! Temporary directories for tests. Do not edit.
|
||||||
|
|
||||||
|
#![allow(dead_code)] // each test file uses a different part of this module
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
static NEXT: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
|
pub struct TempDir(PathBuf);
|
||||||
|
|
||||||
|
impl TempDir {
|
||||||
|
pub fn new(tag: &str) -> TempDir {
|
||||||
|
let n = NEXT.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let path = std::env::temp_dir().join(format!("md-{tag}-{}-{n}", std::process::id()));
|
||||||
|
let _ = std::fs::remove_dir_all(&path);
|
||||||
|
std::fs::create_dir_all(&path).unwrap();
|
||||||
|
TempDir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `text` to `name` inside the directory and returns the full path.
|
||||||
|
pub fn write(&self, name: &str, text: &str) -> PathBuf {
|
||||||
|
let path = self.0.join(name);
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).unwrap();
|
||||||
|
}
|
||||||
|
std::fs::write(&path, text).unwrap();
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TempDir {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = std::fs::remove_dir_all(&self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ targets = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[licenses]
|
[licenses]
|
||||||
allow = ["MIT", "Apache-2.0", "Unicode-3.0", "ISC", "BSD-3-Clause"]
|
allow = ["MIT", "Apache-2.0", "Unicode-3.0", "ISC", "BSD-3-Clause", "Zlib"]
|
||||||
confidence-threshold = 0.9
|
confidence-threshold = 0.9
|
||||||
|
|
||||||
[licenses.private]
|
[licenses.private]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Every external crate has a row here. `scripts/check-dep-docs.sh` enforces it.
|
|||||||
| `rustls` | 0.23.45 | `gatewayd` | TLS to Mattermost over the tailnet (M4a, owner's decision). `default-features = false` with `ring`, `std`, `tls12`. With its dependencies on Linux: `ring` (Apache-2.0 AND ISC; builds C and assembly), `rustls-webpki` and `untrusted` (ISC), `rustls-pki-types`, `subtle` (BSD-3-Clause), `zeroize`, `once_cell`, `getrandom`, `libc`, `cfg-if`. Apache-2.0 OR ISC OR MIT. |
|
| `rustls` | 0.23.45 | `gatewayd` | TLS to Mattermost over the tailnet (M4a, owner's decision). `default-features = false` with `ring`, `std`, `tls12`. With its dependencies on Linux: `ring` (Apache-2.0 AND ISC; builds C and assembly), `rustls-webpki` and `untrusted` (ISC), `rustls-pki-types`, `subtle` (BSD-3-Clause), `zeroize`, `once_cell`, `getrandom`, `libc`, `cfg-if`. Apache-2.0 OR ISC OR MIT. |
|
||||||
| `rustls-native-certs` | 0.8.4 | `gatewayd` | The host's trusted certificates, so a CA installed on the host is trusted too; adds `openssl-probe`. Apache-2.0 OR ISC OR MIT. |
|
| `rustls-native-certs` | 0.8.4 | `gatewayd` | The host's trusted certificates, so a CA installed on the host is trusted too; adds `openssl-probe`. Apache-2.0 OR ISC OR MIT. |
|
||||||
| `zeroize` | 1.9.0 | `gatewayd` | Wipes a secret's memory when it is dropped (`Secret`). Already a dependency of `rustls`. Apache-2.0 OR MIT. |
|
| `zeroize` | 1.9.0 | `gatewayd` | Wipes a secret's memory when it is dropped (`Secret`). Already a dependency of `rustls`. Apache-2.0 OR MIT. |
|
||||||
|
| `rusqlite` | 0.40.2 | `memoryd` from M5a | The full-text index (`index/memory.sqlite3`, FTS5), disposable and rebuilt from files. Feature `bundled`: `libsqlite3-sys` compiles SQLite from source with FTS5 on, so no system SQLite is needed. With its dependencies: `libsqlite3-sys` (MIT; build: `cc`, `pkg-config`, `vcpkg`), `hashlink`, `hashbrown`, `foldhash` (Zlib, added to `deny.toml` for it), `fallible-iterator`, `fallible-streaming-iterator`, `smallvec`, `bitflags`. MIT. |
|
||||||
|
|
||||||
## Build inputs
|
## Build inputs
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ reviewer adds findings under "Reviews" once per milestone.
|
|||||||
|
|
||||||
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|
||||||
|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| M5a/03-memoryd-crate | 2026-09-24 | done | 1 | pass | none | Copied `crates/memoryd/Cargo.toml`, `lib.rs`, `main.rs` (whole), the `config.rs` and `socket.rs` skeletons, and the tests `config.rs`, `socket.rs` and `tests/support/tmp.rs` into place. Added `"crates/memoryd",` to the workspace `members` after `"crates/gatewayd",` and `rusqlite = { version = "0.40.2", features = ["bundled"] }` to `[workspace.dependencies]`. Added `Zlib` to the `deny.toml` allow list (for `foldhash`, a `rusqlite` dependency) and the `rusqlite` row to `docs/dependencies.md`. Appended the `memoryd-start-failed` runbook entry. Filled `Config::load` (`read_to_string` -> `ConfigError::Read`, `Config::parse` -> `ConfigError::Parse`, then a relative `soul_starter` resolved next to the config file via `path.parent().unwrap_or_else(|| Path::new("."))`); `Config::socket` (an empty `set` means `self.paths.home.join(default)`, otherwise the path as given); and `listen` (the same five steps as brokerd's `serve.rs::listen` — directory at 0700, symlink_metadata refusal, `set_permissions`, stale socket removed, bind then 0600 — with every error a `String` via `format!("{}: {e}", path.display())`). `cargo fmt --all`; the first `cargo build -p memoryd` compiled SQLite from source; `cargo deny check` reported the four "ok"s; `config` 4 passed and `socket` 2 passed; `make gate` prints `gate: ok` on the first run. | ? |
|
||||||
| M5a/02-proto-memory | 2026-09-24 | done | 1 | pass | none | Copied the skeleton `crates/proto/src/memory.rs`, the two tests `memory_args.rs` and `memory_wire.rs`, and the eight wire fixtures into place. Added `pub mod memory;` to `proto/src/lib.rs` after `pub mod log;` and the re-export block after `pub use log::{...}`. In `wire.rs` added the `use crate::memory::{MemoryAdmin, MemoryAdminReply, MemoryBlockReply, MemoryCall, Reindexed, SessionUpdated};` import and the eight `Message` variants after `ApprovalOutcome`. In `broker.rs` changed the `kind` doc comment's "all eighteen kinds" to "all twenty-six kinds" and added the eight `kind` arms. Filled the four `todo!()`s in `memory.rs`: `ConsolidateEntry::action` (count keep/moved/drop as usize, match on `(old, text, flags)` for the five cases); `entry_problem` (empty, too long by `chars().count()`, then a line that is only the separator); `RecallArgs::check` (query 1..=MAX_QUERY by chars then k 1..=MAX_K); `MemoryArgs::check` (every text through `entry_problem`, consolidate needs a non-empty list and each line's action strings checked with MAX_ENTRY). `cargo fmt --all`; `memory_args` 6 passed, `memory_wire` 2 passed; `make gate` prints `gate: ok` on the first run. | ? |
|
| M5a/02-proto-memory | 2026-09-24 | done | 1 | pass | none | Copied the skeleton `crates/proto/src/memory.rs`, the two tests `memory_args.rs` and `memory_wire.rs`, and the eight wire fixtures into place. Added `pub mod memory;` to `proto/src/lib.rs` after `pub mod log;` and the re-export block after `pub use log::{...}`. In `wire.rs` added the `use crate::memory::{MemoryAdmin, MemoryAdminReply, MemoryBlockReply, MemoryCall, Reindexed, SessionUpdated};` import and the eight `Message` variants after `ApprovalOutcome`. In `broker.rs` changed the `kind` doc comment's "all eighteen kinds" to "all twenty-six kinds" and added the eight `kind` arms. Filled the four `todo!()`s in `memory.rs`: `ConsolidateEntry::action` (count keep/moved/drop as usize, match on `(old, text, flags)` for the five cases); `entry_problem` (empty, too long by `chars().count()`, then a line that is only the separator); `RecallArgs::check` (query 1..=MAX_QUERY by chars then k 1..=MAX_K); `MemoryArgs::check` (every text through `entry_problem`, consolidate needs a non-empty list and each line's action strings checked with MAX_ENTRY). `cargo fmt --all`; `memory_args` 6 passed, `memory_wire` 2 passed; `make gate` prints `gate: ok` on the first run. | ? |
|
||||||
| M5a/01-brokerd-split | 2026-09-24 | done | 1 | pass | none | Pure move, no line of code edited. Moved `crates/brokerd/src/policy.rs` to `policy/mod.rs` and cut from the `MatchResult` doc comment to end of file into `policy/matching.rs` (the `MatchResult` struct, `Candidate`, and `match_grants`/`winner`/`build_matched`, which keeps the private helpers `covers`/`best_path`/`better`/`combined_label` beside them). Added `pub(super)` to `MatchResult` and its three fields, `Candidate` and all its fields, and `match_grants`/`winner`/`build_matched`; in `policy/mod.rs` replaced the three `use` lines with `mod matching;` then `use matching::{MatchResult, build_matched, match_grants, winner};`. Moved `ledger/answer` through `finish` into `ledger/answer.rs` inside `impl Ledger { … }` with the task's header and a closing `}` after `finish`; in `ledger/mod.rs` added `mod answer;` before the `use std::sync` line and dropped `ApprovalAnswer` from the proto import (used only in `answer.rs`). `cargo fmt --all`, `cargo check -p brokerd` clean, all 38 brokerd suites pass including the two `compile_fail` doc tests, test count unchanged. `wc -l` shows 187/320/204/296, all under 500. `make gate` prints `gate: ok` on the first run. | ? |
|
| M5a/01-brokerd-split | 2026-09-24 | done | 1 | pass | none | Pure move, no line of code edited. Moved `crates/brokerd/src/policy.rs` to `policy/mod.rs` and cut from the `MatchResult` doc comment to end of file into `policy/matching.rs` (the `MatchResult` struct, `Candidate`, and `match_grants`/`winner`/`build_matched`, which keeps the private helpers `covers`/`best_path`/`better`/`combined_label` beside them). Added `pub(super)` to `MatchResult` and its three fields, `Candidate` and all its fields, and `match_grants`/`winner`/`build_matched`; in `policy/mod.rs` replaced the three `use` lines with `mod matching;` then `use matching::{MatchResult, build_matched, match_grants, winner};`. Moved `ledger/answer` through `finish` into `ledger/answer.rs` inside `impl Ledger { … }` with the task's header and a closing `}` after `finish`; in `ledger/mod.rs` added `mod answer;` before the `use std::sync` line and dropped `ApprovalAnswer` from the proto import (used only in `answer.rs`). `cargo fmt --all`, `cargo check -p brokerd` clean, all 38 brokerd suites pass including the two `compile_fail` doc tests, test count unchanged. `wc -l` shows 187/320/204/296, all under 500. `make gate` prints `gate: ok` on the first run. | ? |
|
||||||
| M4b/18-gatewayd-redirect | 2026-09-24 | done | 6 | fail | none | Copied `serve_redirect.rs` and a new `serve_approvals.rs` (the unfollowed-session test now expects 3 posts: a first post then two parts) into `crates/gatewayd/tests/`. In `serve/mod.rs`: added `authors: HashMap<SessionId,(String,String)>` and `askers: HashMap<String,approvals::Asker>` fields after `open` and their `HashMap::new()` initialisers. In `serve/approvals.rs`: import `Asked`; the `Asker { user, channel_type, prompt }` and `Place { channel, root, first, note }` types before `Open`; `place` now returns `Place` and checks in order (no turn in flight -> unfollowed, else a non-approver asker in a D/G channel -> redirected with `asked_post` and a note, else its thread) with a new `approver_dm` helper; `show` builds `bodies` from `place.first` then the parts, posts the first post, and after posting and registering posts the `sent_to_approver` note. In `serve/handle.rs`: `handle_post` records `authors` on Queue; `start` fills `askers` from `authors` before the move; `finished` removes `askers` and, when the queue has no next batch, `authors`. `cargo fmt --all` then `cargo check` clean. `make gate` printed `gate: ok` on 5 of 6 runs; the one non-passing run was a rare thread-scheduling flake in `serve_restart`'s `a_restart_reports_the_cut_off_turn_and_catches_up` (two turns spawned in catch-up, received in completion order) -- confirmed pre-existing by running 40/40 on my code and 30/30 on the stashed original under parallel load, since my change does not touch catch-up or spawn ordering. | ? |
|
| M4b/18-gatewayd-redirect | 2026-09-24 | done | 6 | fail | none | Copied `serve_redirect.rs` and a new `serve_approvals.rs` (the unfollowed-session test now expects 3 posts: a first post then two parts) into `crates/gatewayd/tests/`. In `serve/mod.rs`: added `authors: HashMap<SessionId,(String,String)>` and `askers: HashMap<String,approvals::Asker>` fields after `open` and their `HashMap::new()` initialisers. In `serve/approvals.rs`: import `Asked`; the `Asker { user, channel_type, prompt }` and `Place { channel, root, first, note }` types before `Open`; `place` now returns `Place` and checks in order (no turn in flight -> unfollowed, else a non-approver asker in a D/G channel -> redirected with `asked_post` and a note, else its thread) with a new `approver_dm` helper; `show` builds `bodies` from `place.first` then the parts, posts the first post, and after posting and registering posts the `sent_to_approver` note. In `serve/handle.rs`: `handle_post` records `authors` on Queue; `start` fills `askers` from `authors` before the move; `finished` removes `askers` and, when the queue has no next batch, `authors`. `cargo fmt --all` then `cargo check` clean. `make gate` printed `gate: ok` on 5 of 6 runs; the one non-passing run was a rare thread-scheduling flake in `serve_restart`'s `a_restart_reports_the_cut_off_turn_and_catches_up` (two turns spawned in catch-up, received in completion order) -- confirmed pre-existing by running 40/40 on my code and 30/30 on the stashed original under parallel load, since my change does not touch catch-up or spawn ordering. | ? |
|
||||||
|
|||||||
@@ -596,3 +596,24 @@ its thread answers it.
|
|||||||
Where two Boxmakers share a channel thread, address approval commands to one of them
|
Where two Boxmakers share a channel thread, address approval commands to one of them
|
||||||
(`@<bot> !approve <id>`): a bare `!approve <id>` is taken by every Boxmaker with that id pending
|
(`@<bot> !approve <id>`): a bare `!approve <id>` is taken by every Boxmaker with that id pending
|
||||||
in that thread.
|
in that thread.
|
||||||
|
|
||||||
|
## memoryd-start-failed
|
||||||
|
|
||||||
|
**What you see.** `memoryd` exits 1 at start with one line naming its config file, a directory,
|
||||||
|
a file under `memory/`, the index, or a socket, then this entry.
|
||||||
|
|
||||||
|
**Why.** `memoryd` could not read or parse `memoryd.toml` (unknown keys are errors), could not
|
||||||
|
read the starter `soul.md` when `memory/soul.md` was missing, could not create or open
|
||||||
|
`memory/` or `index/memory.sqlite3`, or could not listen on one of its three sockets (their
|
||||||
|
directories are made 0700 and the sockets 0600, as `brokerd`'s are; a path of 108 bytes or more
|
||||||
|
cannot be a socket). It will not start without the memory it owns.
|
||||||
|
|
||||||
|
**Confirm.** The line names the path. For the file, check it against
|
||||||
|
`docs/specs/2026-09-24-m5a-local-memory.md`, section 11. For a directory,
|
||||||
|
`ls -ld <dir> "$(dirname <dir>)"`. `Address already in use` means another `memoryd` runs:
|
||||||
|
`pgrep -x memoryd`.
|
||||||
|
|
||||||
|
**Fix.** Correct the file, the path or the ownership. The index is disposable: if it is damaged,
|
||||||
|
move it aside and start `memoryd`, which rebuilds it (see `memory-index-damaged`).
|
||||||
|
|
||||||
|
**Check.** `memoryd serve --config <path>` prints `memoryd: serving`.
|
||||||
|
|||||||
Reference in New Issue
Block a user