aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-06 23:55:48 +0100
committertv <tv@krebsco.de>2026-11-06 23:55:48 +0100
commitdf40d6d2aed2d1a3b5c02a0a2e14c7c234d035ef (patch)
treeffe75b73040ca6f4c6a4bc5ef02dd5fc852c758a
parente1ca004e979e038dd1f760f4e26b921e5de743fd (diff)
dashboard: remake switch delay
-rw-r--r--docs/dashboard.md5
-rw-r--r--src/api/dashboard/dashboard.html16
-rw-r--r--src/remake/api.rs2
3 files changed, 18 insertions, 5 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md
index 398a65b..091e9f7 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -280,7 +280,10 @@ this node coordinating it: `compact` (the same content, without its history),
look at first) or `import` (an Automerge document, a backup, uploaded as it is); for the
last two, rules for the writes made meanwhile (a path prefix moved to another, or
dropped; what no rule covers is quarantined on the node that made it), and for any, how
-long votes are collected. The answer shows below: the remake's ID, or why it was refused
+long votes are collected and how long nodes wait after a successful vote before
+switching (the switch delay, `switchDelay`; below a keepalive interval only with "even
+below a keepalive interval" ticked, the API's `?force=switchDelay`: slow nodes may then
+miss the commit before the others switch, and rejoin). The answer shows below: the remake's ID, or why it was refused
(and the API's override, if any). The map shows the votes as they come.
## Console
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index 9a395bb..771b49b 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -366,6 +366,10 @@
</div>
<label>settle <input id="remake-settle" size="6" placeholder="default"
title="How long votes are collected: seconds, or e.g. 500ms, 8m; empty: twice the dormancy threshold"></label>
+ <label>switch delay <input id="remake-switch-delay" size="6" placeholder="default"
+ title="How long nodes wait after the vote succeeded before switching, so the commit reaches every node first: seconds, or e.g. 500ms, 1m; empty: one keepalive interval"></label>
+ <label title="Accept a switch delay below a keepalive interval (?force=switchDelay): slow nodes may miss the commit before the others switch, and have to rejoin"><input
+ type="checkbox" id="remake-force-switch-delay"> even below a keepalive interval</label>
<p class="note">Writes made meanwhile that no rule covers are quarantined on the node
that made them.</p>
<div class="actions"><button type="submit" class="toggle">propose</button>
@@ -2947,13 +2951,17 @@ remakeForm.addEventListener("submit", event => {
event.preventDefault();
const kind = document.getElementById("remake-kind").value;
const settle = document.getElementById("remake-settle").value.trim();
+ const switchDelay = document.getElementById("remake-switch-delay").value.trim();
+ const force = document.getElementById("remake-force-switch-delay").checked
+ ? "force=switchDelay" : "";
let request;
if (kind === "import") {
// The document as it is (octet-stream): its options in the query.
const file = document.getElementById("remake-import-file").files[0];
if (!file) { remakeResult("Choose the Automerge document to import.", true); return; }
- const options = new URLSearchParams();
+ const options = new URLSearchParams(force);
if (settle) options.set("settle", settle);
+ if (switchDelay) options.set("switchDelay", switchDelay);
const rules = remakeRules();
if (rules.length) options.set("rules", JSON.stringify(rules));
const query = options.size ? "?" + options : "";
@@ -2962,6 +2970,7 @@ remakeForm.addEventListener("submit", event => {
} else {
const body = { kind };
if (settle) body.settle = settle;
+ if (switchDelay) body.switchDelay = switchDelay;
if (kind === "replace") {
let parsed;
try {
@@ -2974,7 +2983,7 @@ remakeForm.addEventListener("submit", event => {
const rules = remakeRules();
if (rules.length) body.spec.rules = rules;
}
- request = fetch("remakes", { method: "POST", body: JSON.stringify(body),
+ request = fetch("remakes" + (force ? "?" + force : ""), { method: "POST", body: JSON.stringify(body),
headers: { "Content-Type": "application/json" } });
}
remakeResult("Proposing…", false);
@@ -2986,7 +2995,8 @@ remakeForm.addEventListener("submit", event => {
". The map shows the votes as they come.", false);
} else {
remakeResult(response.status + ": " + (report.error || response.statusText) +
- (report.force ? " (the API takes ?force=" + report.force + ")" : ""), true);
+ (report.force === "switchDelay" ? " (tick \u201ceven below a keepalive interval\u201d)"
+ : report.force ? " (the API takes ?force=" + report.force + ")" : ""), true);
}
})
.catch(error => remakeResult("Proposing failed: " + error, true));
diff --git a/src/remake/api.rs b/src/remake/api.rs
index 8fd0c5b..7888c37 100644
--- a/src/remake/api.rs
+++ b/src/remake/api.rs
@@ -425,7 +425,7 @@ async fn start_kind<H: Host>(
"switchDelay must be at least {} (one keepalive interval), so the commit reaches every node before they switch; add ?force=switchDelay to override",
crate::units::format_duration(keepalive)
),
- None,
+ Some(json!({ "force": "switchDelay" })),
);
}
_ => {}