Skip to main content

Cooperation

Struct Cooperation 

Source
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

Source

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}");
Source

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());
Source

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.

Source

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.

Source

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");
Source

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.

Source

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");
Source

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");
Source

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());
Source

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");
Source

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);
Source

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);
Source

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");
Source

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);
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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"));
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");
Source

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");

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more