pub struct Cooperation { /* private fields */ }Expand description
One runtime’s whole part in the mesh, behind one handle. It holds the single journal this process writes to, the single table of links it has, and the admission lock that decides claims — and it is deliberately one object rather than a set of services, because every one of those is the other’s context: a claim is admitted against state folded from events that arrived over a link, and a link is admitted against trust the same object evaluates.
Every surface — the command line, HTTP, MCP — performs its operations through this, so
there is one place where a session is opened and one where a claim is refused, whoever
asked. Nothing here dials or beats until Cooperation::start; a runtime that is
merely built is a complete, readable, local mesh of one.
use majordomus_cli::mesh::journal::{ClaimMode, SessionInfo};
let session = SessionInfo::named("s1", "cli");
cooperation.claim(&session, vec!["apps".into()], Some("documenting".into()),
ClaimMode::Exclusive, None).unwrap();
// the operation, the event and the folded state are one runtime's, with nobody linked
let state = cooperation.state();
assert_eq!(state.claims[0].intent.as_deref(), Some("documenting"));
assert_eq!(state.sessions.len(), 1, "claiming opened the session it needed");Implementations§
Source§impl Cooperation
impl Cooperation
Sourcepub fn new(setup: CooperationSetup) -> Result<Arc<Self>, MeshError>
pub fn new(setup: CooperationSetup) -> Result<Arc<Self>, MeshError>
Open the journal and build the runtime; nothing dials until Cooperation::start.
The declaration is validated and this runtime’s card is checked here rather than at the first handshake, so a runtime that would be refused by every peer refuses to exist instead of failing one link at a time in the background where nobody reads it. Building it is also what fixes this runtime’s stream: the key it answers to from now on, and the stream its events are written to.
let cooperation = Cooperation::new(setup(5, 30)).unwrap();
assert!(cooperation.peers().is_empty(), "built, and dialing nobody");
assert!(cooperation.journal().own_stream().runtime_key() == cooperation.runtime_key());
// a declaration that no peer could live with is refused here, not at the first link
let Err(refused) = Cooperation::new(setup(30, 5)) else { panic!("a live runtime") };
assert!(refused.to_string().contains("expiry"), "{refused}");Sourcepub fn project_board(&self)
pub fn project_board(&self)
Project the peer board into the journal: an attached session that is new opens a mesh session, a changed announcement releases its previous advisory claim and takes the new one, a withdrawn announcement is released, and a session that left the board is closed. One-way and idempotent — the board stays the local truth, the journal carries it to every linked runtime. Nothing happens when nothing changed.
This is the whole bridge between the board a person reads on one machine and the mesh every other machine reads: a worker announces once, to its own server, and is visible everywhere without knowing that anything else exists.
use majordomus_cli::peers::{ClientInfo, PeerBoard, Transport};
let board = Arc::new(PeerBoard::new());
let worker = board.attach(Transport::Http);
board.identify(&worker, ClientInfo { name: "claude-code".into(), version: "1".into(),
title: None });
board.announce(&worker, "documenting the mesh", vec!["apps/majordomus-cli".into()]);
cooperation.project_board();
let state = cooperation.state();
assert_eq!(state.claims[0].scope, ["apps/majordomus-cli"], "the announcement crossed");
assert_eq!(state.sessions[0].info.client, "claude-code");
// idempotent: projecting an unchanged board writes nothing
cooperation.project_board();
assert_eq!(cooperation.state().digest, state.digest);
// and a worker that leaves takes its claim with it
board.detach(&worker);
cooperation.project_board();
assert!(!cooperation.state().claims[0].state.is_live());Sourcepub fn runtime_key(&self) -> &str
pub fn runtime_key(&self) -> &str
This runtime’s key, <node>-<runtime>: who it is to every other runtime, for as
long as this checkout exists on this machine. It survives a restart — which is what
lets a peer recognise a returning runtime as the same one rather than a second —
while the instance inside its card does not.
Sourcepub fn journal(&self) -> &Arc<Journal> ⓘ
pub fn journal(&self) -> &Arc<Journal> ⓘ
The journal this runtime writes to and every peer’s events land in. Handed out rather than wrapped because reading the mesh is reading events: a caller that wants marks, tallies or the raw stream takes them from here, and the operations above exist for the writes, which must not be done by hand.
Sourcepub fn start(self: &Arc<Self>)
pub fn start(self: &Arc<Self>)
Start the supervisor: the heartbeat, the dialers and the expiry. Never blocks.
It returns before anything has linked, because linking depends on other machines
and a server must answer while it is still finding them. A disabled declaration
starts nothing at all and says so through Cooperation::status, rather than
starting a supervisor that would refuse every round.
cooperation.start();
assert!(!cooperation.stopped(), "the supervisor is up and the caller was not held");
cooperation.stop();
assert!(cooperation.state().digest.len() == 32, "and the journal is still readable");Sourcepub fn stop(&self)
pub fn stop(&self)
Stop every cooperation thread at its next bounded wait and drop every link. The journal stays readable; this runtime’s stream simply stops beating.
Stopping is deliberately not a farewell. Nothing is said to the peers, because a runtime that crashes says nothing either and the mesh must behave the same way in both cases: the streams stop beating, the claims stop excluding after the expiry, and no peer is left believing a promise that a power cut would have broken.
use majordomus_cli::mesh::journal::{ClaimMode, SessionInfo};
let session = SessionInfo::named("s1", "cli");
cooperation.claim(&session, vec!["apps".into()], None, ClaimMode::Exclusive, None).unwrap();
cooperation.stop();
assert!(cooperation.stopped());
assert_eq!(cooperation.state().claims.len(), 1, "the record survives the runtime");begin_stop is the half of this that takes no lock, for a caller
that must not be delayed.
Sourcepub fn begin_stop(&self)
pub fn begin_stop(&self)
Tell every part of this runtime to stop, and return at once.
stop also empties the link table, and to do that it must take the
table’s lock — which a worker can be holding across a dial that waits out the link
timeout. A server shutting down must not wait for that: it has a lease to release,
and a lease released late reads to the next process as a server that is still there.
So the flag, which every loop and every handler checks, is set without any lock, and
the draining is left to stop.
cooperation.begin_stop();
assert!(cooperation.stopped(), "every loop and handler sees it immediately");Sourcepub fn stopped(&self) -> bool
pub fn stopped(&self) -> bool
Whether this runtime has been told to stop. It is checked at the top of every handler, so a stopped runtime refuses a hello and a sync with a typed refusal instead of quietly half-linking while its threads wind down.
use majordomus_cli::mesh::link::{RefusalCode, Signed};
assert!(!cooperation.stopped());
cooperation.stop();
let hello = Signed { body: serde_json::json!({}), sig: String::new() };
let reply = cooperation.accept_hello(&hello);
assert_eq!(reply.refusal.unwrap().code, RefusalCode::NotActive, "refused, not ignored");Sourcepub fn trust_of_key(&self, public_key: &str) -> TrustState
pub fn trust_of_key(&self, public_key: &str) -> TrustState
The trust verdict for a key: this machine’s own key is trusted as itself; any other by the declared policy against what discovery knows of the node.
It is asked twice for every event, once when a link is admitted and again when an event is ingested, because a link admitted an hour ago is not a warrant for what arrives over it now — an event carries the key that signed it, and a key that has since been withdrawn from the allowlist stops being believed without dropping anything.
let stranger = NodeIdentity::ephemeral().unwrap().public.public_key.clone();
// two runtimes of one machine share a key, and neither has to allow the other
assert!(cooperation.trust_of_key(&own_key).is_trusted(), "this machine, twice over");
// and under the default policy an unheard-of key is not trusted by being reachable
assert!(!cooperation.trust_of_key(&stranger).is_trusted());
assert!(!cooperation.trust_of_key("not a key at all").is_trusted());Sourcepub fn retrust(&self, trust: TrustConfig) -> Vec<String>
pub fn retrust(&self, trust: TrustConfig) -> Vec<String>
Put trust in force while this runtime runs, and end every link to a runtime it no
longer trusts. Returns the runtime keys whose links ended.
Before this, a key withdrawn from the declaration kept its links until every runtime
that had admitted it restarted. Now the server calls this whenever it reads a new
generation of the repository: the withdrawn peer’s link is gone at once, its next
sync meets unknown_link, and the hello it says next is refused untrusted. Events it
wrote stay in the journal until the next restart, whose reload applies the trust in
force (I2124).
use majordomus_cli::mesh::config::TrustConfig;
let stranger = NodeIdentity::ephemeral().unwrap().public.public_key;
assert!(c.trust_of_key(&stranger).is_trusted(), "tofu");
assert!(c.retrust(TrustConfig::default()).is_empty(), "no link to end");
assert!(!c.trust_of_key(&stranger).is_trusted(), "deny_unknown is in force now");Sourcepub fn accept_hello(&self, message: &Signed) -> LinkReply
pub fn accept_hello(&self, message: &Signed) -> LinkReply
Answer a hello: the whole admission, in order — shape, signature, freshness, replay, protocol, self, repository, trust, capacity — and a signed welcome.
The order is the point. Everything cheap and unauthenticated is decided before anything expensive, and nothing a stranger says is believed before its signature is checked, so an unsigned flood costs a parse and a refusal. It answers a refusal rather than dropping the message, because a peer that is being refused for its clock, its repository or its key can only fix that if it is told.
// A dials; the transport hands B's accept_hello the signed hello, and B welcomes it
a.dial(&["b:1".into()]).unwrap();
assert_eq!(b.peers()[0].runtime, a.runtime_key(), "admitted, both ways");
// anything that is not a signed hello of this protocol is refused, and named
let nonsense = Signed { body: serde_json::json!({"hello": true}), sig: "00".into() };
let refusal = b.accept_hello(&nonsense).refusal.unwrap();
assert_eq!(refusal.code, RefusalCode::Malformed);Sourcepub fn accept_sync(&self, message: &Signed) -> LinkReply
pub fn accept_sync(&self, message: &Signed) -> LinkReply
Answer a sync round: the link must be known and its peer’s instance current, the signature must be the peer’s and the counter must rise; then ingest, merge, and answer with what the peer lacks.
The rising counter is what makes a captured round worthless: a recorded request replayed later carries a counter that has already been used, and is refused without the events in it being looked at. Answering with what the peer lacks in the same round is what makes the exchange symmetrical — one call, both directions — so convergence does not depend on which side dials.
let key = a.dial(&["b:1".into()]).unwrap();
b.claim(&SessionInfo::named("s1", "cli"), vec!["docs".into()], None,
ClaimMode::Advisory, None).unwrap();
// A asks; B's answer carries B's own events back, so one round settles both sides
a.sync_with(&key).unwrap();
assert_eq!(a.state().claims.len(), 1, "B answered with what A lacked");
assert_eq!(b.status().counters.syncs_in, 1);
// a round under no link this runtime issued is refused before its events are read
let stranger = Signed { body: serde_json::json!({"link": "ff", "counter": 1}), sig: "".into() };
assert_eq!(b.accept_sync(&stranger).refusal.unwrap().code, RefusalCode::Malformed);Sourcepub fn dial(&self, endpoints: &[String]) -> Result<String, RoundError>
pub fn dial(&self, endpoints: &[String]) -> Result<String, RoundError>
Say hello at each endpoint in turn until one welcomes this runtime; verify the welcome as strictly as a hello is verified. Returns the peer’s runtime key.
A peer is dialed by its endpoints rather than by one address because a machine answers at several and only it knows which of them a caller can reach; the key that comes back is what the link is remembered by, so a peer that moves between addresses is still the same peer. Verifying the welcome as strictly as a hello is what keeps the trust decision symmetrical — dialing somebody is not a reason to believe what answers.
// the first address is dead, the second answers: one peer, found at the one that works
let key = a.dial(&["nowhere:1".into(), "b:1".into()]).unwrap();
assert_eq!(key, b.runtime_key());
assert_eq!(a.peers()[0].endpoint.as_deref(), Some("b:1"));
// dialing this runtime's own endpoint is a refusal, not a link to itself
a.dial(&["a:1".into()]).expect_err("a runtime does not cooperate with itself");Sourcepub fn sync_with(&self, runtime_key: &str) -> Result<(), RoundError>
pub fn sync_with(&self, runtime_key: &str) -> Result<(), RoundError>
One sync round with a peer this runtime dials: push what the peer lacks, take what this runtime lacks, in one exchange.
Only the dialing side runs rounds, so a link has one driver and one counter however many threads hold it; an inbound-only peer is synced by its own side. A round is bounded by an event budget rather than by time, so a runtime that has been away for a day catches up over several rounds instead of one that never finishes.
let key = a.dial(&["b:1".into()]).unwrap();
a.claim(&SessionInfo::named("s1", "cli"), vec!["apps".into()], None,
ClaimMode::Exclusive, None).unwrap();
for _ in 0..2 {
a.journal().beat_own();
a.sync_with(&key).unwrap();
}
assert_eq!(a.state().digest, b.state().digest, "the two now hold one state");
// B's own claims come back over the same rounds, without B dialing anybody
b.claim(&SessionInfo::named("s2", "cli"), vec!["docs".into()], None,
ClaimMode::Exclusive, None).unwrap();
b.journal().beat_own();
a.sync_with(&key).unwrap();
assert_eq!(a.state().claims.len(), 2);Sourcepub fn peers(&self) -> Vec<PeerView>
pub fn peers(&self) -> Vec<PeerView>
Every peer this runtime has a link to, including the ones that have expired and not yet been forgotten. An expired peer is kept for a retention window rather than dropped on the spot, because “the peer I had is gone” is the answer a person needs when a machine stops answering, and a list that silently shrinks tells them nothing.
a.dial(&["b:1".into()]).unwrap();
a.dial(&["c:1".into()]).unwrap();
let keys: Vec<String> = a.peers().into_iter().map(|p| p.runtime).collect();
assert_eq!(keys.len(), 2, "one entry per runtime, whoever dialed whom");
assert!(keys.iter().any(|k| k == b.runtime_key()), "B is one of them");
assert!(keys.iter().any(|k| k == c.runtime_key()), "and C is the other");
assert_eq!(b.peers().len(), 1, "B was dialed once and holds one peer");Sourcepub fn status(&self) -> CooperationStatus
pub fn status(&self) -> CooperationStatus
Everything a person or an agent can ask about this runtime, in one reading: what it is, what it speaks, who it is linked to, what it refused, and what it has done.
It is one call rather than several because the parts are only meaningful together — a peer count means nothing without the refusals beside it — and because two calls would be two moments, and a mesh changes between them.
let status = cooperation.status();
assert!(status.active, "this process is cooperating");
assert_eq!(status.repository.unwrap().id, repository.id, "what links are matched on");
assert_eq!(status.endpoints, ["127.0.0.1:9"], "where a peer would dial it");
assert!(status.started_at.is_some(), "and since when");Sourcepub fn state(&self) -> CooperationState
pub fn state(&self) -> CooperationState
The folded state, liveness judged on this runtime’s clock now.
It is computed on every call rather than kept, because the answer changes with the clock and not only with the events: nothing has to happen for a claim to expire, so a cached state would go quietly wrong while nothing was arriving. Two runtimes that hold the same events and agree about who is alive fold the same state, which is what the digest is for.
use majordomus_cli::mesh::journal::{ClaimMode, SessionInfo};
let empty = cooperation.state();
assert!(empty.claims.is_empty());
let session = SessionInfo::named("s1", "cli");
let claim = cooperation
.claim(&session, vec!["apps".into()], None, ClaimMode::Exclusive, None)
.unwrap();
let taken = cooperation.state();
assert!(taken.claims[0].state.is_live());
assert_ne!(taken.digest, empty.digest, "the digest is what a peer is compared to");
cooperation.release(&claim.key).unwrap();
assert!(!cooperation.state().claims[0].state.is_live(), "released, not forgotten");Sourcepub fn streams(&self) -> Vec<(StreamId, StreamLiveness, Option<u64>)>
pub fn streams(&self) -> Vec<(StreamId, StreamLiveness, Option<u64>)>
Every stream’s liveness and the milliseconds since its beat rose — the layer claims actually follow, which is not the layer links live on.
A runtime this one has no link to is alive here as long as its beat keeps arriving
through somebody else, and a runtime with a healthy link is dead here the moment it
stops beating. That is what makes exclusivity survive a relay and a crash alike, and
it is why this is worth reading beside Cooperation::peers rather than instead
of it.
use majordomus_cli::mesh::journal::StreamLiveness;
cooperation.journal().beat_own();
let streams = cooperation.streams();
let (id, liveness, age) = &streams[0];
assert_eq!(id, cooperation.journal().own_stream(), "a runtime hears itself first");
assert_eq!(*liveness, StreamLiveness::Own, "never judged against its own clock");
assert!(age.is_some(), "and it knows when it last beat");Sourcepub fn verify(&self) -> MeshVerifyReport
pub fn verify(&self) -> MeshVerifyReport
Verify cooperation now: the local checks, then one sync round with every peer this runtime dials, and whether both sides hold the same marks afterwards.
It acts rather than reports. Everything else here answers from what happened to arrive; this goes and asks, because the failure it exists to catch is the one where every counter looks reasonable and no traffic is actually crossing. It is therefore safe to run and not free: it writes nothing, but it costs a round per peer.
a.dial(&["b:1".into()]).unwrap();
a.journal().beat_own();
let report = a.verify();
assert!(report.ok, "every check held and every round went through");
assert_eq!(report.peers[0].converged, Some(true), "and the two hold the same marks");
assert_eq!(report.digest, Some(b.state().digest), "which is one state, seen twice");Sourcepub fn open_session(
&self,
info: SessionInfo,
) -> Result<Written, CooperationError>
pub fn open_session( &self, info: SessionInfo, ) -> Result<Written, CooperationError>
Open a session of this runtime, or say again what an open one is doing. One call serves both because a session is identified by its name and described by the rest: announcing a new intent under an existing name is an update every runtime folds the same way, and nothing has to be told apart from what came before.
use majordomus_cli::mesh::journal::SessionInfo;
let opened = cooperation.open_session(SessionInfo::named("s1", "claude-code")).unwrap();
assert!(opened.key.ends_with("/s1"), "qualified by the stream that wrote it");
let with_intent = SessionInfo { intent: Some("documenting the mesh".into()),
..SessionInfo::named("s1", "claude-code") };
cooperation.open_session(with_intent).unwrap();
let sessions = cooperation.state().sessions;
assert_eq!(sessions.len(), 1, "said again, not opened twice");
assert_eq!(sessions[0].info.intent.as_deref(), Some("documenting the mesh"));Sourcepub fn close_session(&self, session: &str) -> Result<Written, CooperationError>
pub fn close_session(&self, session: &str) -> Result<Written, CooperationError>
Close a session of this runtime; its claims end with it.
Claims end with the session rather than having to be released one by one, because the commonest way for a worker to stop is not an orderly one: what a session held must be recoverable from the fact that it is over. Only this runtime may close its own sessions — a session on another runtime is closed by that runtime, or expires when it stops beating.
use majordomus_cli::mesh::journal::{ClaimMode, SessionInfo};
use majordomus_cli::mesh::state::SessionState;
let session = SessionInfo::named("s1", "cli");
cooperation.claim(&session, vec!["apps".into()], None, ClaimMode::Exclusive, None).unwrap();
cooperation.close_session("s1").unwrap();
let state = cooperation.state();
assert_eq!(state.sessions[0].state, SessionState::Closed);
assert!(!state.claims[0].state.is_live(), "nobody had to release it");
// and a name this runtime never opened is not something it can close
cooperation.close_session("s9").expect_err("no such session here");Sourcepub fn claim(
&self,
session: &SessionInfo,
scope: Vec<String>,
intent: Option<String>,
mode: ClaimMode,
issue: Option<String>,
) -> Result<Written, CooperationError>
pub fn claim( &self, session: &SessionInfo, scope: Vec<String>, intent: Option<String>, mode: ClaimMode, issue: Option<String>, ) -> Result<Written, CooperationError>
Claim a scope for a session of this runtime. An exclusive claim that meets a live exclusive claim of another session — on any runtime this one has heard — is refused with the claims it meets; the session is opened first if it is new.
This is where exclusion actually happens. Admission is checked against the folded state of every runtime this one has heard from, under a lock, so two callers here cannot both be told yes; and an advisory claim is never refused, because its purpose is to make two workers visible to each other rather than to stop either.
use majordomus_cli::mesh::journal::{ClaimMode, SessionInfo};
let first = SessionInfo::named("s1", "cli");
cooperation.claim(&first, vec!["apps/majordomus-cli".into()],
Some("the mesh module".into()), ClaimMode::Exclusive, Some("#184".into())).unwrap();
// a scope under a held one is the same place, and another session is refused it
let second = SessionInfo::named("s2", "cli");
cooperation.claim(&second, vec!["apps/majordomus-cli/src".into()], None,
ClaimMode::Exclusive, None).expect_err("the scope is claimed");
// announcing the same work advisedly is always allowed: it informs, it does not exclude
cooperation.claim(&second, vec!["apps/majordomus-cli/src".into()], None,
ClaimMode::Advisory, None).unwrap();
assert_eq!(cooperation.state().overlaps.len(), 1, "two workers, told about each other");Sourcepub fn release(&self, claim_key: &str) -> Result<Written, CooperationError>
pub fn release(&self, claim_key: &str) -> Result<Written, CooperationError>
Release a claim this runtime took in its current run.
Only the holder releases: a claim is a statement by one runtime about what it is doing, and letting another runtime withdraw it would make the record say something its author never said. A dead holder is not a problem this needs to solve — its stream stops beating and its claims stop excluding on every runtime at once.
use majordomus_cli::mesh::journal::{ClaimMode, SessionInfo};
let mine = SessionInfo::named("s1", "cli");
let held = cooperation
.claim(&mine, vec!["apps".into()], None, ClaimMode::Exclusive, None)
.unwrap();
cooperation.release(&held.key).unwrap();
// the scope is free again, for anybody
let other = SessionInfo::named("s2", "cli");
cooperation.claim(&other, vec!["apps".into()], None, ClaimMode::Exclusive, None).unwrap();
// a claim of another runtime is not this one's to withdraw
cooperation.release("somebody-0000000000000002/c-abc").expect_err("not its author");Sourcepub fn publish_handover(
&self,
handover: HandoverBody,
) -> Result<Written, CooperationError>
pub fn publish_handover( &self, handover: HandoverBody, ) -> Result<Written, CooperationError>
Publish a handover for every linked runtime to pick up.
A handover is identified by the digest of what it says, so publishing the same continuity twice — by a retry, or by two runtimes that both saw it — leaves one handover rather than a choice between duplicates for whoever comes to take it.
use majordomus_cli::mesh::journal::HandoverBody;
let body = "# Objective\nfinish the mesh docs\n".to_string();
let handover = HandoverBody { id: HandoverBody::digest_of(&body), task: None, issue: None,
milestone: None, branch: Some("feature/mesh".into()), head: None, created_at: None,
name: None, body };
let written = cooperation.publish_handover(handover.clone()).unwrap();
assert_eq!(written.key, handover.id, "addressed by what it says");
cooperation.publish_handover(handover).unwrap();
assert_eq!(cooperation.state().handovers.len(), 1, "published twice, and still one");Sourcepub fn consume_handover(
&self,
id: &str,
session: &str,
) -> Result<(HandoverView, Written), CooperationError>
pub fn consume_handover( &self, id: &str, session: &str, ) -> Result<(HandoverView, Written), CooperationError>
Take a handover: return its body and record, for everyone, that this session took it. The record is the point. Two workers picking up the same continuity is the failure this prevents, and it can only be prevented by the taking being visible — so reading a handover and consuming it are one operation, and a second worker sees who was there before it decides.
Taking one already taken is not refused, because a handover may legitimately be picked up again; the record simply names everybody who has.
use majordomus_cli::mesh::journal::HandoverBody;
let body = "# Objective\nfinish the mesh docs\n".to_string();
let id = HandoverBody::digest_of(&body);
cooperation.publish_handover(HandoverBody { id: id.clone(), task: None, issue: None,
milestone: None, branch: None, head: None, created_at: None, name: None, body }).unwrap();
let (taken, _written) = cooperation.consume_handover(&id, "s1").unwrap();
assert!(taken.handover.body.contains("mesh docs"), "the continuity itself");
assert_eq!(cooperation.state().handovers[0].consumed_by.len(), 1, "and who took it");
cooperation.consume_handover("no-such-handover", "s1").expect_err("nothing to take");Sourcepub fn request_review(
&self,
session: &str,
subject: String,
scope: Vec<String>,
issue: Option<String>,
reviewer: Option<String>,
body: Option<String>,
) -> Result<Written, CooperationError>
pub fn request_review( &self, session: &str, subject: String, scope: Vec<String>, issue: Option<String>, reviewer: Option<String>, body: Option<String>, ) -> Result<Written, CooperationError>
Ask for a review; a named reviewer must be a linked peer carrying reviews.
A request may name nobody, and then it is an offer to the whole mesh — which is the useful shape when a worker wants a second opinion and does not care whose. Naming a reviewer is checked here rather than left to fail silently later: a request addressed to a runtime that is not linked, or to one whose executable does not do reviews, would otherwise sit unanswered and look like indifference.
use majordomus_cli::mesh::state::ReviewState;
cooperation.request_review("s1", "feature/mesh-cooperation".into(),
vec!["apps/majordomus-cli/src/mesh".into()], Some("#184".into()), None, None).unwrap();
let review = &cooperation.state().reviews[0];
assert_eq!(review.subject, "feature/mesh-cooperation");
assert_eq!(review.state, ReviewState::Open, "asked of anybody, answered by nobody yet");
// a reviewer nobody is linked to is refused now, not left waiting
cooperation.request_review("s1", "feature/x".into(), vec![], None,
Some("somebody-0000000000000002".into()), None).expect_err("no such peer");Sourcepub fn answer_review(
&self,
request: &str,
session: &str,
verdict: String,
note: Option<String>,
) -> Result<Written, CooperationError>
pub fn answer_review( &self, request: &str, session: &str, verdict: String, note: Option<String>, ) -> Result<Written, CooperationError>
Answer a review request — anybody’s, on any runtime, except the session that asked.
The one refusal here is self-review, because a verdict from the session under review is not a second pair of eyes and a record that allowed it would make every review in the mesh worth less. Several sessions may answer one request and their verdicts all stand: the disagreement is the information.
use majordomus_cli::mesh::state::ReviewState;
let asked = cooperation
.request_review("s1", "feature/mesh-cooperation".into(), vec![], None, None, None)
.unwrap();
cooperation.answer_review(&asked.key, "s2", "approved".into(),
Some("the examples run".into())).unwrap();
let review = &cooperation.state().reviews[0];
assert_eq!(review.state, ReviewState::Answered);
assert_eq!(review.answers[0].verdict, "approved");
// the session under review does not get a vote on itself
cooperation.answer_review(&asked.key, "s1", "approved".into(), None)
.expect_err("a review is another session's verdict");Sourcepub fn withdraw_review(
&self,
request: &str,
session: &str,
note: Option<String>,
) -> Result<Written, CooperationError>
pub fn withdraw_review( &self, request: &str, session: &str, note: Option<String>, ) -> Result<Written, CooperationError>
End a review request this runtime asked for: done, void or superseded, with an optional result the request then carries. Any session of the asking runtime may — an MCP session is one connection, and the asker that reconnects is another session of the same runtime — and no other runtime may: a withdrawal is the requester’s own record, never a verdict, so it does not count as an answer.
use majordomus_cli::mesh::state::ReviewState;
let asked = cooperation
.request_review("s1", "pre-flight of #919".into(), vec![], None, None,
Some("structure job and own cases, hook environment unset".into()))
.unwrap();
// a request of another runtime cannot be ended here
cooperation.withdraw_review("other-0000000000000009/r-1", "s1", None)
.expect_err("not this runtime's request");
cooperation.withdraw_review(&asked.key, "s1", Some("clean: 45 gates, 12 cases".into()))
.unwrap();
let review = &cooperation.state().reviews[0];
assert_eq!(review.state, ReviewState::Withdrawn);
assert_eq!(review.result.as_deref(), Some("clean: 45 gates, 12 cases"));
assert!(review.answers.is_empty(), "a result is not an answer");