From ff9e7c244c270853a8a8f58ee950859866358e1d Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 18:20:13 +0000 Subject: remake: aborts written as the user who asks; no abort by time Review 2, N3: 1150 let any member write an abort decision once a remake had expired a grace period ago, judged by the change's own time, the writer's word. A member could race the coordinator's legitimate commit with an abort dated as it liked; both taken, Automerge shows one, and nodes that acted on the commit split from those that didn't. The two writers of such aborts are operator requests whose endpoints already need write on _continuum/remakes: aborting an expired remake (DELETE /admin/remakes/{id}) and superseding one (POST /admin/remakes?force=supersede). Both now commit as the user who asks (the proposal and its vote with them, for a supersede), which the rules take as one who may write the decision. The clause judging an abort by its time goes, and with it the time the rules carried. An expired remake is still aborted through any member, by an operator. --- src/remake/proposal.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/remake/proposal.rs') diff --git a/src/remake/proposal.rs b/src/remake/proposal.rs index 7695b4c..f8b3b0e 100644 --- a/src/remake/proposal.rs +++ b/src/remake/proposal.rs @@ -86,6 +86,9 @@ pub(super) struct ExpiredRemake { pub(super) struct Supersede { pub(super) force: Force, pub(super) active_peers: HashSet, + /// Who asks (the API's user): the proposal, and the decisions aborting the remakes + /// it supersedes, are written as theirs, as the rules judge them (`access::rules`). + pub(super) by: Option, } #[derive(Debug, thiserror::Error)] @@ -126,8 +129,9 @@ pub(super) async fn propose( let me = host.node_id(); let started = host.remake().started_at().plus_ms(grace); let base_at_commit = kind::by_name(&proposal.kind).is_some_and(|kind| kind.base_at_commit()); + let by = supersede.by.clone(); let record = host - .commit_doc(move |doc| -> Result { + .commit_doc_for(by, move |doc| -> Result { let mut all = records::load_all(doc); if let Some((open, _)) = all.iter().find(|(_, record)| { record.proposal.from_epoch == proposal.from_epoch -- cgit v1.3.1