Skip to main content

max / makenotwork

code_smoke: advertise localhost, and fix multithreaded's clippy Two more failures from gates that had never run, each uncovered by fixing the one in front of it. code_smoke got past MissingCdnBaseUrl and panicked further along, building WebAuthn: "Failed to create WebauthnBuilder: Configuration" (server main.rs:427). The server derives its relying-party id from HOST_URL's host, and WebauthnBuilder::new validates that id against Url::domain(), which is None for an IP literal. So HOST_URL=http://127.0.0.1:<port> could never boot this binary — a ceiling on the gate, not a preference. The advertised origin is now http://localhost:<port>; HOST stays 127.0.0.1, since that is the bind address and the gate probes loopback directly. Verified by hand against the gate's exact invocation: migrate+seed exits 0, then the server logs "WebAuthn initialized (rp_id=localhost)" and "listening" and answers GET /health with 200 — both of the signals code_smoke asserts. The env test now also rejects an IP-literal HOST_URL rather than only checking the prefix, so the next person who "simplifies" this back to 127.0.0.1 fails here instead of in a 30-minute gate. clippy: server and mnw-cli now pass, so the gate advanced to multithreaded and found six more of the same lint family — a panic! arg in tests/harness/client.rs and five assert! args across workflows/mentions.rs and workflows/search.rs. Same masking as before: clippy stops at the first failing unit, so each fix reveals the next crate. Only rustc 1.97's wider useless_borrows_in_formatting sees these; they are invisible on an older toolchain.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-03 20:35 UTC
Signed with PGP, not checked
Commit: 56389f66eaa6b45e1729d27de1929492f8d53276
Parent: 8ef89ce
6 files changed, +46 insertions, -14 deletions
@@ -1611,7 +1611,7 @@
1611 1611
1612 1612 [[package]]
1613 1613 name = "sando-daemon"
1614 - version = "0.2.6"
1614 + version = "0.2.7"
1615 1615 dependencies = [
1616 1616 "anyhow",
1617 1617 "async-trait",
@@ -2978,10 +2978,6 @@
2978 2978 name = "tagtree"
2979 2979 version = "0.4.0"
2980 2980
2981 - [[patch.unused]]
2982 - name = "supernote-push"
2983 - version = "0.1.0"
2984 -
2985 2981 [[patch.unused]]
2986 2982 name = "docengine"
2987 2983 version = "0.4.0"
@@ -2993,3 +2989,7 @@
2993 2989 [[patch.unused]]
2994 2990 name = "synckit-config"
2995 2991 version = "0.1.2"
2992 +
2993 + [[patch.unused]]
2994 + name = "supernote-push"
2995 + version = "0.1.0"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "sando-daemon"
3 - version = "0.2.6"
3 + version = "0.2.7"
4 4 edition = "2024"
5 5 license = "MIT"
6 6
@@ -357,7 +357,7 @@
357 357 impl TestResponse {
358 358 pub(crate) fn json<T: serde::de::DeserializeOwned>(&self) -> T {
359 359 serde_json::from_str(&self.text)
360 - .unwrap_or_else(|e| panic!("Failed to parse JSON: {}\nBody: {}", e, &self.text))
360 + .unwrap_or_else(|e| panic!("Failed to parse JSON: {}\nBody: {}", e, self.text))
361 361 }
362 362
363 363 pub(crate) fn header(&self, name: &str) -> Option<&str> {
@@ -49,7 +49,7 @@
49 49 assert!(
50 50 resp.text.contains("/p/test/u/mentionee"),
51 51 "Expected mention to render as profile link. Body: {}",
52 - &resp.text[resp.text.find("post-body").unwrap_or(0)..]
52 + resp.text[resp.text.find("post-body").unwrap_or(0)..]
53 53 .chars()
54 54 .take(500)
55 55 .collect::<String>()
@@ -233,7 +233,7 @@
233 233 assert!(
234 234 resp.text.contains("badge-mention"),
235 235 "Expected badge-mention class in category listing. Body snippet: {}",
236 - &resp.text[resp.text.find("data-table").unwrap_or(0)..]
236 + resp.text[resp.text.find("data-table").unwrap_or(0)..]
237 237 .chars()
238 238 .take(800)
239 239 .collect::<String>()
@@ -301,7 +301,7 @@
301 301 assert!(
302 302 resp.text.contains("badge-mention"),
303 303 "Expected badge-mention in tracked page. Body snippet: {}",
304 - &resp.text[resp.text.find("data-table").unwrap_or(0)..]
304 + resp.text[resp.text.find("data-table").unwrap_or(0)..]
305 305 .chars()
306 306 .take(800)
307 307 .collect::<String>()
@@ -60,7 +60,7 @@
60 60 assert!(
61 61 resp.text.contains("Unique Flamingo Discussion"),
62 62 "Expected thread title in search results. Body: {}",
63 - &resp.text
63 + resp.text
64 64 );
65 65 assert!(
66 66 !resp.text.contains("Other Thread"),
@@ -89,7 +89,7 @@
89 89 assert!(
90 90 resp.text.contains("Generic Title"),
91 91 "Thread with matching body should appear. Body: {}",
92 - &resp.text
92 + resp.text
93 93 );
94 94 }
95 95
@@ -1747,7 +1747,17 @@
1747 1747 /// is the `config_check_env_file` guard's job, on the node, against that node's
1748 1748 /// real env file.
1749 1749 fn code_smoke_env(cmd: &mut tokio::process::Command, ctx: &GateCtx, db_url: &str) {
1750 - let origin = format!("http://127.0.0.1:{}", ctx.cfg.code_smoke_port);
1750 + // `localhost`, not `127.0.0.1`, and the distinction is load-bearing. The
1751 + // server derives its WebAuthn relying-party id from HOST_URL's host, and
1752 + // `WebauthnBuilder::new` validates that id against `Url::domain()` — which
1753 + // is `None` for an IP literal, so an origin of `http://127.0.0.1:<port>`
1754 + // fails with WebauthnError::Configuration before the server ever binds. It
1755 + // is a real ceiling on the gate, not a preference: no IP-literal origin can
1756 + // boot this binary. `localhost` is a domain, and matches the derived rp_id.
1757 + //
1758 + // HOST stays 127.0.0.1: that is the bind address, and the gate probes the
1759 + // loopback address directly, so only the advertised origin changes.
1760 + let origin = format!("http://localhost:{}", ctx.cfg.code_smoke_port);
1751 1761 cmd.env("DATABASE_URL", db_url)
1752 1762 .env("HOST", "127.0.0.1")
1753 1763 .env("PORT", ctx.cfg.code_smoke_port.to_string())
@@ -2411,6 +2421,20 @@
2411 2421 }
2412 2422 }
2413 2423
2424 + /// True when the URL's host parses as a domain rather than an IP literal,
2425 + /// which is the distinction `Url::domain()` draws and WebAuthn depends on.
2426 + fn url_host_is_a_domain(url: &str) -> bool {
2427 + let after = url.split("://").nth(1).unwrap_or("");
2428 + let host = after.split(['/', '?', '#']).next().unwrap_or("");
2429 + let host = host.rsplit('@').next().unwrap_or(host);
2430 + let host = if let Some(rest) = host.strip_prefix('[') {
2431 + rest.split(']').next().unwrap_or("")
2432 + } else {
2433 + host.split(':').next().unwrap_or("")
2434 + };
2435 + !host.is_empty() && host.parse::<std::net::IpAddr>().is_err()
2436 + }
2437 +
2414 2438 fn resolving_ctx(worktree: &str, aux: &[(&str, &str)]) -> GateCtx {
2415 2439 GateCtx {
2416 2440 pool: SqlitePool::connect_lazy("sqlite::memory:").unwrap(),
@@ -3582,7 +3606,15 @@
3582 3606 }
3583 3607 // Loopback, so Config::from_env's is_production branch stays false and
3584 3608 // the gate never trips MissingPublicBucket for want of an S3 bucket.
3585 - assert!(set["HOST_URL"].starts_with("http://127.0.0.1"));
3609 + assert!(set["HOST_URL"].starts_with("http://localhost"));
3610 + // Not an IP literal: the server derives its WebAuthn rp_id from this
3611 + // host, and WebauthnBuilder rejects an origin whose Url::domain() is
3612 + // None, which is every IP address. An IP here cannot boot the server.
3613 + assert!(
3614 + url_host_is_a_domain(&set["HOST_URL"]),
3615 + "HOST_URL host must be a domain, not an IP literal: {}",
3616 + set["HOST_URL"],
3617 + );
3586 3618 assert_eq!(set["HOST"], "127.0.0.1");
3587 3619 // The signing secret has to clear the server's 32-char floor, or the
3588 3620 // gate fails with WeakSigningSecret instead of testing anything.