build: a bare {chain} in a mail body goes, on its own line or inline

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-09-24 09:33:21 +02:00
co-authored by Claude Fable 5.1
parent 88e54ed17f
commit 0a02b4db1a
+17 -7
View File
@@ -1045,14 +1045,23 @@ fn keep_links(fallback: &Value, merged: &mut Value) {
for (bucket, stages) in fb { for (bucket, stages) in fb {
let Some(ms) = mb.get_mut(bucket).and_then(Value::as_object_mut) else { continue }; let Some(ms) = mb.get_mut(bucket).and_then(Value::as_object_mut) else { continue };
for (stage, spec) in stages.as_object().into_iter().flatten() { for (stage, spec) in stages.as_object().into_iter().flatten() {
let Some(link) = spec["body"].as_str().and_then(|b| b.lines().find(|l| l.contains("{site}/decide/"))) else { continue }; let link = spec["body"].as_str().and_then(|b| b.lines().find(|l| l.contains("{site}/decide/")));
let Some(body) = ms.get_mut(stage).and_then(|m| m.get_mut("body")) else { continue }; let Some(body) = ms.get_mut(stage).and_then(|m| m.get_mut("body")) else { continue };
let Some(text) = body.as_str() else { continue }; let Some(text) = body.as_str() else { continue };
if text.contains("/decide/") { // A bare `{chain}` outside a link is the record id pasted
continue; // into prose, which a model does when told to keep
// placeholders; it goes, on its own line or inline.
let cleaned: Vec<String> = text
.lines()
.map(|l| if l.contains("/decide/") { l.to_string() } else { l.replace("{chain}", "").split_whitespace().collect::<Vec<_>>().join(" ") })
.collect();
let mut cleaned = cleaned.join("\n").trim_end().to_string();
if let Some(link) = link {
if !cleaned.contains("/decide/") {
cleaned = format!("{cleaned}\n\n{link}");
}
} }
let kept: Vec<&str> = text.lines().filter(|l| l.trim() != "{chain}").collect(); *body = Value::String(cleaned);
*body = Value::String(format!("{}\n\n{link}", kept.join("\n").trim_end()));
} }
} }
} }
@@ -1723,11 +1732,12 @@ mod tests {
#[test] #[test]
fn a_models_mail_keeps_the_submitters_link() { fn a_models_mail_keeps_the_submitters_link() {
let fallback = json!({ "leaks": { "open": { "subject": "s", "body": "Hello,\n\nGot it.\n\nWhat you can do next: {site}/decide/leak?chain={chain}&email={email}\n\nI at i@b.example" } } }); let fallback = json!({ "leaks": { "open": { "subject": "s", "body": "Hello,\n\nGot it.\n\nWhat you can do next: {site}/decide/leak?chain={chain}&email={email}\n\nI at i@b.example" } } });
let mut merged = json!({ "leaks": { "open": { "subject": "Got it", "body": "Hi,\n\nWe have it.\n\n{chain}" } } }); let mut merged = json!({ "leaks": { "open": { "subject": "Got it", "body": "Hi,\n\nWe have it. {chain}\n\n{chain}" } } });
keep_links(&fallback, &mut merged); keep_links(&fallback, &mut merged);
let body = merged["leaks"]["open"]["body"].as_str().unwrap(); let body = merged["leaks"]["open"]["body"].as_str().unwrap();
assert!(body.ends_with("What you can do next: {site}/decide/leak?chain={chain}&email={email}")); assert!(body.ends_with("What you can do next: {site}/decide/leak?chain={chain}&email={email}"));
assert!(!body.contains("\n{chain}")); assert!(!body.contains("it. {chain}") && !body.contains("\n{chain}"));
assert!(body.starts_with("Hi,\n\nWe have it."));
// A body that kept its link is left alone. // A body that kept its link is left alone.
let mut fine = json!({ "leaks": { "open": { "subject": "s", "body": "Withdraw here: {site}/decide/leak?chain={chain}&email={email}" } } }); let mut fine = json!({ "leaks": { "open": { "subject": "s", "body": "Withdraw here: {site}/decide/leak?chain={chain}&email={email}" } } });
keep_links(&fallback, &mut fine); keep_links(&fallback, &mut fine);