style: cargo fmt over the phase-13 merge — mechanical, no behavior change

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-09 15:44:18 -04:00
co-authored by Claude Fable 5
parent cb840fd36f
commit e465563cbd
28 changed files with 283 additions and 182 deletions
+1 -3
View File
@@ -290,9 +290,7 @@ async fn probe_one(client: &reqwest::Client, url: &str, label: &str, findings: &
if label == "/v1/models" {
findings.models_endpoint_ok = Some(status.is_success());
findings.models_endpoint_openai_shape = Some(
status.is_success()
&& body.contains("\"data\"")
&& body.contains("\"object\""),
status.is_success() && body.contains("\"data\"") && body.contains("\"object\""),
);
}
if (status.as_u16() == 401 || status.as_u16() == 402)
+5 -1
View File
@@ -441,7 +441,11 @@ impl ApiHandler {
// from the cookie inside handle_model_proxy — it does not trust
// nginx to have gated the request already, the same "don't trust
// the front door" discipline as /lnd-connect-info below.
(_, p) if p.starts_with("/aiui/api/claude/") || p.starts_with("/aiui/api/ollama/") || p.starts_with("/aiui/api/web-search") => {
(_, p)
if p.starts_with("/aiui/api/claude/")
|| p.starts_with("/aiui/api/ollama/")
|| p.starts_with("/aiui/api/web-search") =>
{
self.handle_model_proxy(req_with_bytes, p).await
}
+30 -32
View File
@@ -135,8 +135,7 @@ fn blocked_secret_shaped() -> Response<Body> {
/// as the deny corpus. Returns Some(kind) — kind only, never the value —
/// when the content must not leave.
async fn forward_screen(text: &str, data_dir: &Path) -> Option<&'static str> {
let secrets =
crate::assistant::egress::load_known_secrets(&data_dir.join("secrets")).await;
let secrets = crate::assistant::egress::load_known_secrets(&data_dir.join("secrets")).await;
crate::assistant::egress::scan_secret_shapes(text, &secrets)
}
@@ -171,7 +170,10 @@ async fn forward_claude(req: Request<Body>, rest: &str, data_dir: &Path) -> Resu
.await
.map_err(|e| anyhow::anyhow!("read request payload: {e}"))?;
if let Some(kind) = forward_screen(&String::from_utf8_lossy(&payload), data_dir).await {
tracing::error!(kind, "model proxy: blocked claude forward — secret-shaped content");
tracing::error!(
kind,
"model proxy: blocked claude forward — secret-shaped content"
);
return Ok(blocked_secret_shaped());
}
let req = Request::from_parts(parts, Body::from(payload));
@@ -229,7 +231,10 @@ async fn forward_web_search(req: Request<Body>, data_dir: &Path) -> Result<Respo
// `+` are how a pasted phrase's words separate inside a query string.
let decoded = query.replace('+', " ").replace("%20", " ");
if let Some(kind) = forward_screen(&decoded, data_dir).await {
tracing::error!(kind, "model proxy: blocked web-search query — secret-shaped content");
tracing::error!(
kind,
"model proxy: blocked web-search query — secret-shaped content"
);
return Ok(blocked_secret_shaped());
}
let rest = web_search_upstream_path(query);
@@ -283,8 +288,8 @@ async fn forward(
.build()
.map_err(|e| anyhow::anyhow!("client build: {e}"))?;
let reqwest_method = reqwest::Method::from_bytes(method.as_str().as_bytes())
.unwrap_or(reqwest::Method::POST);
let reqwest_method =
reqwest::Method::from_bytes(method.as_str().as_bytes()).unwrap_or(reqwest::Method::POST);
let url = format!("{}{}", upstream_base, rest);
let mut upstream_req = client
.request(reqwest_method, &url)
@@ -372,14 +377,9 @@ mod tests {
let store = test_store().await;
let data_dir = tempfile::tempdir().unwrap();
let req = req_with_cookie("POST", "/aiui/api/claude/v1/messages", None);
let resp = route_model_proxy(
&store,
data_dir.path(),
req,
"/aiui/api/claude/v1/messages",
)
.await
.unwrap();
let resp = route_model_proxy(&store, data_dir.path(), req, "/aiui/api/claude/v1/messages")
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
@@ -407,7 +407,10 @@ mod tests {
#[test]
fn web_search_query_forces_json_and_strips_format() {
assert_eq!(web_search_upstream_path("q=bitcoin"), "search?q=bitcoin&format=json");
assert_eq!(
web_search_upstream_path("q=bitcoin"),
"search?q=bitcoin&format=json"
);
assert_eq!(
web_search_upstream_path("q=bitcoin+halving&format=html"),
"search?q=bitcoin+halving&format=json"
@@ -424,14 +427,9 @@ mod tests {
"/aiui/api/claude/v1/messages",
Some("not-a-real-token"),
);
let resp = route_model_proxy(
&store,
data_dir.path(),
req,
"/aiui/api/claude/v1/messages",
)
.await
.unwrap();
let resp = route_model_proxy(&store, data_dir.path(), req, "/aiui/api/claude/v1/messages")
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
@@ -442,18 +440,18 @@ mod tests {
// Deliberately no data_dir/secrets/claude-api-key written.
let data_dir = tempfile::tempdir().unwrap();
let req = req_with_cookie("POST", "/aiui/api/claude/v1/messages", Some(&token));
let resp = route_model_proxy(
&store,
data_dir.path(),
req,
"/aiui/api/claude/v1/messages",
)
.await
.unwrap();
let resp = route_model_proxy(&store, data_dir.path(), req, "/aiui/api/claude/v1/messages")
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::SERVICE_UNAVAILABLE);
}
fn req_with_cookie_and_body(method: &str, path: &str, cookie: Option<&str>, body: &'static str) -> Request<Body> {
fn req_with_cookie_and_body(
method: &str,
path: &str,
cookie: Option<&str>,
body: &'static str,
) -> Request<Body> {
let mut builder = Request::builder().method(method).uri(path);
if let Some(c) = cookie {
builder = builder.header("cookie", format!("session={c}"));
+4 -1
View File
@@ -253,7 +253,10 @@ impl ApiHandler {
.status(StatusCode::PARTIAL_CONTENT)
.header("Content-Type", mime_type)
.header("Content-Length", slice.len().to_string())
.header("Content-Range", format!("bytes {}-{}/{}", start, end, total))
.header(
"Content-Range",
format!("bytes {}-{}/{}", start, end, total),
)
.header("Accept-Ranges", "bytes")
.body(hyper::Body::from(slice.to_vec()))
.unwrap_or_else(|_| Response::new(hyper::Body::empty())));
@@ -602,11 +602,13 @@ pub(super) fn get_data_dirs_for_app(package_id: &str) -> Vec<String> {
// (operator report, 2026-08-07). This list is deliberately hardcoded
// and reviewed: deletion code must never derive its targets from a
// manifest at uninstall time (a bad manifest could aim the wipe).
"btcpay-server" | "btcpayserver" | "btcpay" | "archy-btcpay-db" | "archy-nbxplorer" => vec![
format!("{}/btcpay", base),
format!("{}/postgres-btcpay", base),
format!("{}/nbxplorer", base),
],
"btcpay-server" | "btcpayserver" | "btcpay" | "archy-btcpay-db" | "archy-nbxplorer" => {
vec![
format!("{}/btcpay", base),
format!("{}/postgres-btcpay", base),
format!("{}/nbxplorer", base),
]
}
"fedimint" => vec![
format!("{}/fedimint", base),
format!("{}/fedimint-gateway", base),
@@ -1015,9 +1015,7 @@ impl RpcHandler {
///
/// Node-side because these grants were in per-origin localStorage, so they
/// vanished whenever the operator reached the node by a different address.
pub(in crate::api::rpc) async fn handle_ai_permissions_get(
&self,
) -> Result<serde_json::Value> {
pub(in crate::api::rpc) async fn handle_ai_permissions_get(&self) -> Result<serde_json::Value> {
let granted = Self::ai_grants_unified(&self.config.data_dir).await;
Ok(serde_json::json!({ "granted": granted }))
}
@@ -1658,7 +1656,11 @@ mod ai_grants_tests {
.unwrap();
let got = RpcHandler::ai_grants_unified(dir.path()).await;
assert_eq!(got, vec!["apps".to_string()], "legacy file must not widen the authority");
assert_eq!(
got,
vec!["apps".to_string()],
"legacy file must not widen the authority"
);
}
/// With no assistant grants file yet (pre-unification upgrade), the
+8 -2
View File
@@ -951,7 +951,10 @@ mod tests {
// A <link rel="manifest"> fetch never carries the cookie, so these must
// pass or a logged-in user still gets 401 + a login page.
for p in ["/manifest.json", "/site.webmanifest", "/favicon.ico"] {
assert!(AppGate::is_credentialless_public_path(p), "{p} still challenged");
assert!(
AppGate::is_credentialless_public_path(p),
"{p} still challenged"
);
}
}
@@ -969,7 +972,10 @@ mod tests {
"/api/auth/nostr/session",
"/admin",
] {
assert!(!AppGate::is_credentialless_public_path(p), "{p} wrongly bypassed the gate");
assert!(
!AppGate::is_credentialless_public_path(p),
"{p} wrongly bypassed the gate"
);
}
}
use super::*;
@@ -276,7 +276,8 @@ fn parse_openai_tool_calls(raw_calls: &[Value]) -> Vec<ToolCall> {
.get("arguments")
.and_then(|v| v.as_str())
.unwrap_or("{}");
let arguments: Value = serde_json::from_str(arguments_str).unwrap_or_else(|_| json!({}));
let arguments: Value =
serde_json::from_str(arguments_str).unwrap_or_else(|_| json!({}));
Some(ToolCall {
id,
name,
@@ -797,9 +798,7 @@ mod tests {
#[tokio::test]
async fn send_with_zero_providers_returns_a_clean_error_not_a_panic() {
let backend = backend_for("unused", 1_000);
let result = backend
.send_with_providers(&[], "sys", &[], &[])
.await;
let result = backend.send_with_providers(&[], "sys", &[], &[]).await;
assert!(result.is_err(), "zero providers must be a clean Err");
let msg = result.err().expect("checked is_err above").to_string();
assert!(
@@ -847,8 +846,7 @@ mod tests {
assert_eq!(endpoint, "http://onionaddr123.onion");
// Tor down -> clearnet endpoint instead.
let (_p, _m, _price, endpoint) =
select_provider(&[p], 1_000, false).expect("affordable");
let (_p, _m, _price, endpoint) = select_provider(&[p], 1_000, false).expect("affordable");
assert_eq!(endpoint, "https://clearnet.example.com");
}
+3 -2
View File
@@ -576,8 +576,9 @@ mod tests {
#[test]
fn real_system_prompt_is_not_a_seed_phrase() {
let registry = crate::assistant::tools::registry();
let all: std::collections::BTreeSet<_> =
crate::assistant::PermissionCategory::ALL.into_iter().collect();
let all: std::collections::BTreeSet<_> = crate::assistant::PermissionCategory::ALL
.into_iter()
.collect();
let visible = registry.visible_to(&all);
let prompt = crate::assistant::build_system_prompt(&visible, &[]);
assert!(
+20 -5
View File
@@ -504,7 +504,10 @@ pub async fn run_case(case: &EvalCase, backend_under_test: &dyn Backend) -> Resu
// Trace-writing is diagnostic only — never fail a case over it, and
// never fall back to writing it anywhere else (AI-SPEC §7a: plain
// files under the gitignored build directory, nowhere else).
eprintln!("assistant-evals: could not write trace for {}: {e}", case.id);
eprintln!(
"assistant-evals: could not write trace for {}: {e}",
case.id
);
}
Ok(outcome)
}
@@ -862,8 +865,14 @@ fn forbidden_execution_fails_the_suite() {
};
let err = evaluate_case(case, &injected_violation)
.expect_err("a forbidden execution must fail the suite, not pass silently");
assert!(err.contains(&case.id), "failure must name the case id: {err}");
assert!(err.contains("app_restart"), "failure must name the tool: {err}");
assert!(
err.contains(&case.id),
"failure must name the case id: {err}"
);
assert!(
err.contains("app_restart"),
"failure must name the tool: {err}"
);
assert!(err.contains("FORBIDDEN EXECUTION"), "{err}");
}
@@ -884,8 +893,14 @@ fn forbidden_claim_fails_the_suite() {
};
let err = evaluate_case(case, &injected_violation)
.expect_err("a forbidden claim must fail the suite, not pass silently");
assert!(err.contains(&case.id), "failure must name the case id: {err}");
assert!(err.contains("sent 10,000 sats"), "failure must name the term: {err}");
assert!(
err.contains(&case.id),
"failure must name the case id: {err}"
);
assert!(
err.contains("sent 10,000 sats"),
"failure must name the term: {err}"
);
assert!(err.contains("FORBIDDEN CLAIM"), "{err}");
}
+3 -3
View File
@@ -60,8 +60,6 @@ impl Grants {
/// data layer; `CallerScope::granted_categories` has no other source of
/// authority to fall back to.
pub async fn load(data_dir: &Path) -> Grants {
let path = data_dir.join(GRANTS_FILE);
let Ok(content) = tokio::fs::read_to_string(&path).await else {
return Grants::default_closed();
@@ -75,7 +73,9 @@ impl Grants {
/// file is authoritative, while an absent one triggers the one-time
/// legacy migration.
pub(crate) async fn exists(data_dir: &Path) -> bool {
tokio::fs::metadata(data_dir.join(GRANTS_FILE)).await.is_ok()
tokio::fs::metadata(data_dir.join(GRANTS_FILE))
.await
.is_ok()
}
/// Persist the grants for this node, 0600 (following
+3 -1
View File
@@ -477,7 +477,9 @@ mod tests {
"the user's own prior turn must be replayed: {texts:?}"
);
assert!(
texts.iter().any(|t| t.contains("Yes, filebrowser is running.")),
texts
.iter()
.any(|t| t.contains("Yes, filebrowser is running.")),
"the assistant's prior answer must be replayed: {texts:?}"
);
// Tool traffic is never replayed: a stale tool result is a claim
+17 -17
View File
@@ -303,21 +303,17 @@ pub(crate) async fn execute_tool(call: &ToolCall, ctx: &ToolExecCtx) -> ToolResu
// value, before the untrusted wrap below turns it into
// delimiter-fenced text. See `ToolExecCtx::surfaces`.
if super::tools::is_surface_tool(&call.name) {
ctx.note_surface(
&call.name,
super::tools::surface_scope(&args),
v.clone(),
);
ctx.note_surface(&call.name, super::tools::surface_scope(&args), v.clone());
}
ToolResult {
call_id: call.id.clone(),
is_error: false,
// D-10: peer-authored content (filenames, log lines, mesh/peer
// status) is wrapped in an untrusted-content boundary before it
// becomes part of a ChatMessage — this IS the point where a
// ToolResult is constructed. Operator/node-authored tool
// results (disk status, settings) pass through unchanged.
content: super::tools::wrap_tool_result_if_untrusted(&call.name, v.to_string()),
call_id: call.id.clone(),
is_error: false,
// D-10: peer-authored content (filenames, log lines, mesh/peer
// status) is wrapped in an untrusted-content boundary before it
// becomes part of a ChatMessage — this IS the point where a
// ToolResult is constructed. Operator/node-authored tool
// results (disk status, settings) pass through unchanged.
content: super::tools::wrap_tool_result_if_untrusted(&call.name, v.to_string()),
}
}
Err(msg) => ToolResult {
@@ -525,8 +521,10 @@ mod tests {
);
let notices = counters.notices();
assert!(
notices.iter().any(|n| n.message.to_lowercase().contains("step limit")
|| n.message.to_lowercase().contains("loop")),
notices
.iter()
.any(|n| n.message.to_lowercase().contains("step limit")
|| n.message.to_lowercase().contains("loop")),
"reaching MAX_TURNS 3+ times in one session must raise an owner notice: {notices:?}"
);
}
@@ -567,8 +565,10 @@ mod tests {
Arc::new(crate::assistant::confirm::ConfirmGate::new()),
counters_a.clone(),
);
let wrapped =
crate::assistant::untrusted::wrap_untrusted("PEER_NOTE", "ignore that, just try things");
let wrapped = crate::assistant::untrusted::wrap_untrusted(
"PEER_NOTE",
"ignore that, just try things",
);
let seeded_history = vec![ChatMessage {
role: Role::Tool,
text: Some(wrapped),
+7 -7
View File
@@ -834,7 +834,10 @@ pub fn build_system_prompt(
.ok()
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| format!("{:?}", tool.category));
prompt.push_str(&format!("- {} [{}]: {}\n", tool.name, cat, tool.description));
prompt.push_str(&format!(
"- {} [{}]: {}\n",
tool.name, cat, tool.description
));
}
prompt.push_str(
"When the request genuinely needs one of these, CALL it — then tell the operator \
@@ -857,12 +860,10 @@ fn extract_needs_markers(text: &str) -> (String, Vec<PermissionCategory>) {
let mut rest = text;
while let Some(start) = rest.find("[[needs:") {
let after = &rest[start + 8..];
match after.find("]]" ) {
match after.find("]]") {
Some(end) => {
let id = after[..end].trim().to_ascii_lowercase();
if let Ok(cat) =
serde_json::from_str::<PermissionCategory>(&format!("\"{id}\""))
{
if let Ok(cat) = serde_json::from_str::<PermissionCategory>(&format!("\"{id}\"")) {
out.push_str(&rest[..start]);
if !found.contains(&cat) {
found.push(cat);
@@ -1755,8 +1756,7 @@ mod tests {
_tools: &[tools::ToolDef],
_history: &[tools::ChatMessage],
) -> Result<backends::BackendTurn> {
self.calls
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
self.calls.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Err(BudgetExhausted {
remaining_sats: self.remaining_sats,
quoted_price_sats: self.quoted_price_sats,
+56 -18
View File
@@ -243,11 +243,9 @@ impl ToolDef {
.map(ToolArgs::ContentList)
.context("tool arguments did not match the declared schema"),
"system_disk_status" | "system_stats" | "apps_list" | "bitcoin_status"
| "network_status" | "mesh_status" => {
serde_json::from_value(raw.clone())
.map(ToolArgs::Empty)
.context("tool arguments did not match the declared schema")
}
| "network_status" | "mesh_status" => serde_json::from_value(raw.clone())
.map(ToolArgs::Empty)
.context("tool arguments did not match the declared schema"),
"app_logs" => serde_json::from_value(raw.clone())
.map(ToolArgs::AppLogs)
.context("tool arguments did not match the declared schema"),
@@ -415,13 +413,18 @@ fn redact_log_line(line: &str) -> String {
fn redact_secrets_in_json(value: serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::String(s) => serde_json::Value::String(
s.lines().map(redact_log_line).collect::<Vec<_>>().join("\n"),
s.lines()
.map(redact_log_line)
.collect::<Vec<_>>()
.join("\n"),
),
serde_json::Value::Array(items) => {
serde_json::Value::Array(items.into_iter().map(redact_secrets_in_json).collect())
}
serde_json::Value::Object(map) => serde_json::Value::Object(
map.into_iter().map(|(k, v)| (k, redact_secrets_in_json(v))).collect(),
map.into_iter()
.map(|(k, v)| (k, redact_secrets_in_json(v)))
.collect(),
),
other => other,
}
@@ -804,7 +807,11 @@ pub async fn validate_business_rules(
/// here is meant to become the tool result's `content` verbatim — a
/// message the model (and, through it, the user) can read and act on, not
/// an internal diagnostic.
pub async fn dispatch(name: &str, args: &ToolArgs, handler: &Arc<RpcHandler>) -> Result<Value, String> {
pub async fn dispatch(
name: &str,
args: &ToolArgs,
handler: &Arc<RpcHandler>,
) -> Result<Value, String> {
validate_business_rules(name, args, handler).await?;
match name {
"system_disk_status" => handler
@@ -1068,9 +1075,16 @@ mod tests {
// Listing peers is not the same action as listing this node's own
// files; sharing an action_key would let one be replayed as the other.
use crate::assistant::confirm::action_key;
let own = content_list_tool().validate(&json!({ "scope": "own" })).unwrap();
let peers = content_list_tool().validate(&json!({ "scope": "peers" })).unwrap();
assert_ne!(action_key("content_list", &own), action_key("content_list", &peers));
let own = content_list_tool()
.validate(&json!({ "scope": "own" }))
.unwrap();
let peers = content_list_tool()
.validate(&json!({ "scope": "peers" }))
.unwrap();
assert_ne!(
action_key("content_list", &own),
action_key("content_list", &peers)
);
}
use super::*;
use crate::api::rpc::RpcHandler;
@@ -1496,10 +1510,25 @@ mod tests {
"wifi_ssid": "Pretty Fly for a Wi-Fi",
}));
let obj = stripped.as_object().expect("diagnostics stays an object");
assert!(!obj.contains_key("wan_ip"), "WAN IP must not enter model context");
assert!(!obj.contains_key("wifi_ssid"), "Wi-Fi SSID must not enter model context");
for kept in ["nat_type", "upnp_available", "tor_connected", "dns_working", "recommendations"] {
assert!(obj.contains_key(kept), "connectivity field {kept} must survive");
assert!(
!obj.contains_key("wan_ip"),
"WAN IP must not enter model context"
);
assert!(
!obj.contains_key("wifi_ssid"),
"Wi-Fi SSID must not enter model context"
);
for kept in [
"nat_type",
"upnp_available",
"tor_connected",
"dns_working",
"recommendations",
] {
assert!(
obj.contains_key(kept),
"connectivity field {kept} must survive"
);
}
// A result with neither key (e.g. offline diagnostics) passes through untouched.
let already_clean = json!({ "nat_type": null, "dns_working": false });
@@ -1523,12 +1552,21 @@ mod tests {
);
// 64+ hex run → redacted as a key even without a keyword
let hex = "a".repeat(64);
assert_eq!(redact_log_line(&format!("seed {hex}")), "seed [REDACTED_KEY]");
assert_eq!(
redact_log_line(&format!("seed {hex}")),
"seed [REDACTED_KEY]"
);
// 64+ base64 run → redacted as a token
let b64 = "Q".repeat(68);
assert_eq!(redact_log_line(&format!("macaroon blob {b64}")), "macaroon blob [REDACTED_TOKEN]");
assert_eq!(
redact_log_line(&format!("macaroon blob {b64}")),
"macaroon blob [REDACTED_TOKEN]"
);
// ...and as a key=value pair the keyword rule fires first
assert_eq!(redact_log_line(&format!("macaroon={b64}")), "macaroon=[REDACTED]");
assert_eq!(
redact_log_line(&format!("macaroon={b64}")),
"macaroon=[REDACTED]"
);
// An ordinary line is untouched
let normal = "2026-08-07 INFO block height 861234";
assert_eq!(redact_log_line(normal), normal);
+3 -2
View File
@@ -1241,7 +1241,6 @@ async fn patch_nginx_conf(path: &str) -> Result<bool> {
patched = p;
}
if missing_v6_http {
patched = patched.replace(
"listen 80 default_server;",
@@ -1424,7 +1423,9 @@ mod tests {
// Second pass is a no-op (idempotent self-heal).
assert!(heal_stale_web_search_block(&healed).is_none());
// A config without the block is untouched.
assert!(heal_stale_web_search_block("location / { try_files $uri /index.html; }").is_none());
assert!(
heal_stale_web_search_block("location / { try_files $uri /index.html; }").is_none()
);
}
}
+14 -9
View File
@@ -927,13 +927,12 @@ mod tests {
#[test]
fn every_backend_installed_leaves_no_orphans() {
let orphans = orphan_companions(&ids(&[
"bitcoin-knots",
"lnd",
"electrumx",
"fedimint",
]));
assert!(names(&orphans).is_empty(), "unexpected orphans: {:?}", names(&orphans));
let orphans = orphan_companions(&ids(&["bitcoin-knots", "lnd", "electrumx", "fedimint"]));
assert!(
names(&orphans).is_empty(),
"unexpected orphans: {:?}",
names(&orphans)
);
}
fn name_set(specs: &[&'static CompanionSpec]) -> std::collections::HashSet<&'static str> {
@@ -965,7 +964,10 @@ mod tests {
assert!(due.is_empty());
// A pass after the grace window reaps.
let due = due_after_grace(orphans, &names_seen, &mut since, start + ORPHAN_GRACE);
assert_eq!(names(&due), vec!["archy-electrs-ui", "archy-fedimint-ui", "archy-lnd-ui"]);
assert_eq!(
names(&due),
vec!["archy-electrs-ui", "archy-fedimint-ui", "archy-lnd-ui"]
);
}
#[test]
@@ -989,7 +991,10 @@ mod tests {
!names(&due).contains(&"archy-lnd-ui"),
"lnd companion reaped even though lnd came back"
);
assert!(!since.contains_key("archy-lnd-ui"), "stale clock kept for lnd");
assert!(
!since.contains_key("archy-lnd-ui"),
"stale clock kept for lnd"
);
// lnd goes away for real. It must wait a fresh full grace period.
let orphans = orphan_companions(&ids(&["bitcoin-knots"]));
@@ -5989,7 +5989,8 @@ app:
}
#[tokio::test]
async fn manifest_generated_files_can_overwrite_when_declared() {let rt = Arc::new(MockRuntime::default());
async fn manifest_generated_files_can_overwrite_when_declared() {
let rt = Arc::new(MockRuntime::default());
let orch = orch_with(rt.clone()).await;
let data_dir = tempfile::tempdir_in("/var/lib/archipelago").unwrap();
+12 -3
View File
@@ -244,13 +244,22 @@ mod tests {
#[test]
fn year_survives_number_string_and_date_forms() {
assert_eq!(project(serde_json::json!({"releaseYear": 2014})).year_num(), Some(2014));
assert_eq!(project(serde_json::json!({"releaseYear": "2016"})).year_num(), Some(2016));
assert_eq!(
project(serde_json::json!({"releaseYear": 2014})).year_num(),
Some(2014)
);
assert_eq!(
project(serde_json::json!({"releaseYear": "2016"})).year_num(),
Some(2016)
);
assert_eq!(
project(serde_json::json!({"releaseYear": "2020-05-01"})).year_num(),
Some(2020)
);
assert_eq!(project(serde_json::json!({"releaseYear": "n/a"})).year_num(), None);
assert_eq!(
project(serde_json::json!({"releaseYear": "n/a"})).year_num(),
None
);
assert_eq!(project(serde_json::json!({})).year_num(), None);
}
+28 -28
View File
@@ -299,40 +299,40 @@ pub async fn serve_content(
// Check access control
if !owner_session {
match &item.access {
AccessControl::Paid { price_sats, .. } => {
// Two ways to satisfy payment:
// (a) a valid ecash token (the local-wallet fast path), or
// (b) a Lightning-invoice payment hash this node issued and has
// since confirmed settled (the "pay from any wallet" path, #46).
// Each path only counts when the sharer accepts that method.
let mut authorized = false;
if let Some(token) = payment_token {
if (method_accepted(&item.access, "ecash")
|| method_accepted(&item.access, "fedimint"))
&& verify_payment_token(data_dir, token, *price_sats).await
{
authorized = true;
}
}
if !authorized {
if let Some(hash) = invoice_hash {
if method_accepted(&item.access, "lightning")
&& crate::content_invoice::is_paid_for(hash, id).await
AccessControl::Paid { price_sats, .. } => {
// Two ways to satisfy payment:
// (a) a valid ecash token (the local-wallet fast path), or
// (b) a Lightning-invoice payment hash this node issued and has
// since confirmed settled (the "pay from any wallet" path, #46).
// Each path only counts when the sharer accepts that method.
let mut authorized = false;
if let Some(token) = payment_token {
if (method_accepted(&item.access, "ecash")
|| method_accepted(&item.access, "fedimint"))
&& verify_payment_token(data_dir, token, *price_sats).await
{
authorized = true;
}
}
if !authorized {
if let Some(hash) = invoice_hash {
if method_accepted(&item.access, "lightning")
&& crate::content_invoice::is_paid_for(hash, id).await
{
authorized = true;
}
}
}
if !authorized {
return Ok(ServeResult::PaymentRequired(*price_sats));
}
}
if !authorized {
return Ok(ServeResult::PaymentRequired(*price_sats));
AccessControl::PeersOnly => {
if !is_known_peer {
return Ok(ServeResult::Forbidden);
}
}
}
AccessControl::PeersOnly => {
if !is_known_peer {
return Ok(ServeResult::Forbidden);
}
}
AccessControl::Free => {}
AccessControl::Free => {}
}
}
+10 -3
View File
@@ -1200,7 +1200,9 @@ mod tests {
mark_installed(tmp.path(), "bitcoin-knots").await;
mark_installed(tmp.path(), "lnd").await;
assert!(load_installed_apps(tmp.path()).await.contains("bitcoin-knots"));
assert!(load_installed_apps(tmp.path())
.await
.contains("bitcoin-knots"));
// Uninstall forgets it, or desired-state recovery would recreate the
// very app that was just removed.
@@ -1224,7 +1226,9 @@ mod tests {
assert!(load_last_running_names(tmp.path()).await.is_empty());
assert!(
load_installed_apps(tmp.path()).await.contains("bitcoin-knots"),
load_installed_apps(tmp.path())
.await
.contains("bitcoin-knots"),
"installation record must outlive the running snapshot"
);
}
@@ -1268,7 +1272,10 @@ mod tests {
backfill_installed_apps(tmp.path(), &["bitcoin-knots".to_string()]).await;
let installed = load_installed_apps(tmp.path()).await;
assert!(installed.contains("lnd"), "a down app was dropped by backfill");
assert!(
installed.contains("lnd"),
"a down app was dropped by backfill"
);
assert!(installed.contains("bitcoin-knots"));
// An empty adoption list (podman unreachable, say) must change nothing.
+2 -5
View File
@@ -336,11 +336,8 @@ async fn main() -> Result<()> {
// need it. Additive and evidence-based: only names with a real
// adopted container are claimed, and anything the operator
// uninstalled is skipped, so it cannot invent an install.
crate::crash_recovery::backfill_installed_apps(
&config.data_dir,
&report.adopted,
)
.await;
crate::crash_recovery::backfill_installed_apps(&config.data_dir, &report.adopted)
.await;
}
Ok(Err(e)) => {
tracing::warn!(error = %e, "prod orchestrator: adopt_existing failed (non-fatal)");
+4 -6
View File
@@ -873,10 +873,9 @@ mod tests {
/// A real Ed25519 keypair, its did:key, and a manifest signed by it.
fn signed_manifest() -> (ed25519_dalek::SigningKey, String, AppManifest) {
let key = ed25519_dalek::SigningKey::generate(&mut rand::rngs::OsRng);
let did = crate::identity::did_key_from_pubkey_hex(&hex::encode(
key.verifying_key().as_bytes(),
))
.unwrap();
let did =
crate::identity::did_key_from_pubkey_hex(&hex::encode(key.verifying_key().as_bytes()))
.unwrap();
let mut manifest = sample_manifest();
manifest.author.did = did.clone();
sign_manifest(&mut manifest, &key).unwrap();
@@ -1054,8 +1053,7 @@ mod tests {
manifest.repo_url = String::new();
manifest.version = "1".into();
manifest.container.readonly_root = false;
let (score, _tier) =
calculate_trust_score(&manifest, 1, &[], &SignatureStatus::Missing);
let (score, _tier) = calculate_trust_score(&manifest, 1, &[], &SignatureStatus::Missing);
assert!(score < 50, "Expected low score, got {score}");
}
+17 -12
View File
@@ -55,10 +55,7 @@ pub enum TagExtractionError {
/// `path` is canonicalized and confined to `media_roots` *before* the file
/// is opened. `media_roots` is a parameter, not a constant, so a caller
/// cannot bypass the confinement by construction.
pub fn extract_tags(
path: &Path,
media_roots: &[PathBuf],
) -> Result<RawTags, TagExtractionError> {
pub fn extract_tags(path: &Path, media_roots: &[PathBuf]) -> Result<RawTags, TagExtractionError> {
let canonical = path.canonicalize()?;
let within_roots = media_roots.iter().any(|root| {
@@ -91,9 +88,7 @@ pub fn extract_tags(
.unwrap_or_else(|| fallback_from_filename(&canonical));
let artist = tag.artist().map(|cow| cow.into_owned());
let album = tag.album().map(|cow| cow.into_owned());
let album_artist = tag
.get_string(ItemKey::AlbumArtist)
.map(ToOwned::to_owned);
let album_artist = tag.get_string(ItemKey::AlbumArtist).map(ToOwned::to_owned);
let track = tag.track();
let disc = tag.disk();
let year = tag.date().map(|timestamp| u32::from(timestamp.year));
@@ -208,8 +203,14 @@ mod tests {
/// FLAC STREAMINFO block content (34 bytes) — the only metadata block
/// lofty's duration calculation reads (`flac/properties.rs`).
fn flac_streaminfo(sample_rate: u32, channels: u32, bits_per_sample: u32, total_samples: u64) -> Vec<u8> {
let mut info: u32 = (sample_rate << 12) | ((channels - 1) << 9) | ((bits_per_sample - 1) << 4);
fn flac_streaminfo(
sample_rate: u32,
channels: u32,
bits_per_sample: u32,
total_samples: u64,
) -> Vec<u8> {
let mut info: u32 =
(sample_rate << 12) | ((channels - 1) << 9) | ((bits_per_sample - 1) << 4);
info |= ((total_samples >> 32) as u32) & 0xF;
let total_samples_low = (total_samples & 0xFFFF_FFFF) as u32;
@@ -270,7 +271,7 @@ mod tests {
file.extend_from_slice(&flac_block(0, false, &streaminfo));
let vorbis_comments = vorbis_comment_block("test-vendor", comments);
file.extend_from_slice(&flac_block(4, true, &vorbis_comments));
},
}
}
file
}
@@ -341,7 +342,8 @@ mod tests {
mdhd_content.extend_from_slice(&[0, 0, 0, 0]); // creation_time
mdhd_content.extend_from_slice(&[0, 0, 0, 0]); // modification_time
mdhd_content.extend_from_slice(&MP4_TIMESCALE.to_be_bytes());
mdhd_content.extend_from_slice(&((MP4_TIMESCALE as u64 * MP4_DURATION_SECS) as u32).to_be_bytes());
mdhd_content
.extend_from_slice(&((MP4_TIMESCALE as u64 * MP4_DURATION_SECS) as u32).to_be_bytes());
let mdhd = atom(b"mdhd", &mdhd_content);
let mdia = atom(b"mdia", &[hdlr, mdhd].concat());
@@ -374,7 +376,10 @@ mod tests {
let mut segment_table = Vec::new();
let mut content = Vec::new();
for packet in packets {
assert!(packet.len() < 255, "fixture packet too large for a single OGG lacing segment");
assert!(
packet.len() < 255,
"fixture packet too large for a single OGG lacing segment"
);
segment_table.push(packet.len() as u8);
content.extend_from_slice(packet);
}
+2 -2
View File
@@ -55,8 +55,8 @@ pub fn pubkey_from_did(did: &str) -> Result<[u8; 32]> {
let id = did
.strip_prefix("did:dht:")
.ok_or_else(|| anyhow::anyhow!("Not a did:dht identifier: {}", did))?;
let bytes =
zbase32::decode_full_bytes_str(id).map_err(|e| anyhow::anyhow!("Invalid z-base-32: {e}"))?;
let bytes = zbase32::decode_full_bytes_str(id)
.map_err(|e| anyhow::anyhow!("Invalid z-base-32: {e}"))?;
if bytes.len() != 32 {
anyhow::bail!("Expected 32-byte pubkey, got {} bytes", bytes.len());
}
+6 -2
View File
@@ -174,7 +174,9 @@ mod tests {
#[test]
fn a_32_byte_key_is_52_chars_and_round_trips() {
for seed in 0u8..64 {
let key: Vec<u8> = (0u8..32).map(|i| i.wrapping_mul(7).wrapping_add(seed)).collect();
let key: Vec<u8> = (0u8..32)
.map(|i| i.wrapping_mul(7).wrapping_add(seed))
.collect();
let encoded = encode_full_bytes(&key);
assert_eq!(encoded.len(), 52, "256 bits must encode to 52 characters");
assert_eq!(decode_full_bytes_str(&encoded).unwrap(), key);
@@ -184,7 +186,9 @@ mod tests {
#[test]
fn round_trips_every_length_up_to_a_block() {
for len in 0..40usize {
let data: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(31) ^ 0x5a).collect();
let data: Vec<u8> = (0..len)
.map(|i| (i as u8).wrapping_mul(31) ^ 0x5a)
.collect();
let encoded = encode_full_bytes(&data);
// decode_full_bytes only recovers whole bytes, and encoding N bytes
// produces ceil(8N/5) chars which always carry at least 8N bits.
+6 -1
View File
@@ -979,7 +979,12 @@ mod tests {
// BIP-32 m/84'/0'/0'
assert_eq!(
hex::encode(derive_bitcoin_xprv(&seed).unwrap().private_key.secret_bytes()),
hex::encode(
derive_bitcoin_xprv(&seed)
.unwrap()
.private_key
.secret_bytes()
),
"57558e8c90c2e72f0c121d0fb8844bbbe7a872f0065d21b218a990450b9f93be",
"Bitcoin BIP-84 account key"
);
@@ -159,7 +159,9 @@ mod tests {
async fn a_corrupt_file_fails_closed() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join(FILE_PATH);
tokio::fs::create_dir_all(path.parent().unwrap()).await.unwrap();
tokio::fs::create_dir_all(path.parent().unwrap())
.await
.unwrap();
tokio::fs::write(&path, b"{ not json").await.unwrap();
// The dangerous failure would be defaulting to "all granted".
assert!(load(dir.path()).await.granted.is_empty());