Skip to main content

API_MINOR

Constant API_MINOR 

Source
pub const API_MINOR: u32 = 43;
Expand description

The integer MINOR of API_VERSION — see there. Bumped from 0 to 1 when params validation became strict (#34); to 2 with the set_nickname verb + StatusResult.self_nickname (#37); to 3 when allow/grant strings became STABLE principals — b64u:/eid:/roster names, never nicknames (#38); to 4 with the set_app_metadata verb + PresencePeer.meta (#39); to 5 with PeerReachability.meta — pairing-mode app metadata on the probe pong (#40); to 6 with PeerInfo.principal — the peer’s eid: device principal on status (#41); to 7 with PeerReachability.principal — the same on reachability rows (#42); to 8 with the service_allow_grant/service_allow_revoke per-peer access verbs (#44); to 9 covering the unregister_service (#50) / peer_services (#52) / Run env+cwd (#51) surface that shipped in 0.10.1 without a bump, PLUS the set_relays live relay-set verb (#53); to 10 when service_allow_revoke/peer_remove became IMMEDIATE — no verb shape changed, but their observable contract did: a revoked principal’s next session is refused even on a connection it already holds, and its live connections are severed. Previously both waited for the peer to disconnect on its own, which is unbounded (#54). A consumer can guard on api_minor >= 10 before telling a user that revocation has taken effect; to 11 when service_allow_grant/service_allow_revoke gained EPHEMERAL-service support and became strict about an unknown service name — a name in neither the config nor the ephemeral registry now answers ERR_NO_SUCH_SERVICE instead of a silent {} (#55, #69); to 12 with the pushed StreamFrame::Reachability liveness transition frame (#58); to 13 with PeerReachability::path — direct-vs-relay attribution on every reachability row (#64); to 14 with the run-backend MCPMESH_PEER_EID identity var — the caller’s stable device principal, unconditionally present, so a run server can scope per caller without keying on a nickname (#60); to 15 with the blob_revoke / blob_unpublish verbs — per-scope withdrawal of a grant and of a published hash, so un-sharing a file no longer requires unpairing the person (#62); to 16 when the app-blob provider became available in PAIRING mode — the blob verbs previously errored on any daemon without an org root key, though their scope gate never needed one (#61); to 17 when the service answer began coming from the LIVE registry rather than config + overlay, so a grant the accept path would refuse is no longer advertised. Three surfaces share that resolver and all changed together: status‘s services[].allow, peer_services’ name list, and the mcpmesh/ping/1 probe’s services. No wire shape changed, only the source of truth — exactly the class of change a downstream cannot see in a type diff (#100); to 18 with blob_republish, so a fetched blob can be re-served and every recipient becomes a source (#83); to 19 with durable blob revocation — an unpublish now survives a later republish via a per-scope withdrawal set, and ERR_BLOB_WITHDRAWN distinguishes “deliberately withdrawn” from “never had it” (#107); to 20 with blob_list filters + paging AND a DEFAULT limit of 256 scopes (the clamp is 4096) — a daemon with more scopes than that previously answered with everything, and past the 16 MiB frame cap the CLIENT rejected the response as malformed, leaving the caller an opaque failure with no way to page. The connection survived: the control surface carries no strike bound. This is a behaviour change for existing callers, detectable via the new total/truncated (#84b); to 21 when a PATH change became a reachability transition — StreamFrame::Reachability stopped being an up/down toggle and same-verdict frames became possible (#92); to 22 with a SECOND producer for that frame: a live per-session watcher that pushes when a session’s selected path changes, rather than waiting for a probe, at a cadence probes never had (#92); to 23 when PeerReachability::rtt_ms stopped including the path-settle window — a relayed peer could previously never report under 600ms, so “relayed AND fast” was unreachable by construction (#123); to 24 when reachable stopped sharing a deadline with path classification — a relayed peer whose pong arrived after ~2.4s was reported OFFLINE while it was answering (#128); to 25 with ActiveSession::principal — the live-session view was keyed on a display nickname, so two devices under one nickname were indistinguishable and any UI acting on a session (revoke, disconnect, inspect) keyed on a collidable string (#73); to 26 when a rate-limited inbound NOTIFICATION stopped being silently dropped and became a recorded audit event — no type changed; the observable audit stream did (#76, #139); to 27 with the audit_prune / audit_list verbs, StatusResult::storage, and the opt-in [limits].audit_retain_months boot retention — the audit log stopped being a permanent, unbounded, unreadable record (#88); to 28 with StatusResult::self_network / StreamFrame::SelfNetwork / the snapshot’s copy — the node’s OWN reachability posture, previously unanswerable from either side of the API (#90); to 29 with AuditRecord::principal — stable identity on the event stream and the on-disk log, resolving #57’s parked docs conflict in favour of the #41/#42/#73 line (the audit surface bans secrets and raw hex, not the prefixed principal rendering); to 30 with StreamFrame::Reachability’s source — the frame has had TWO producers since 22 with no way to tell them apart, so an embedder could not distinguish “a throwaway dial went via a relay” from “the link this call is on just degraded”, and had to hedge every message down to the weaker claim. rtt_ms: None was never the discriminator the doc implied (#150); to 31 with ERR_NICKNAME_TAKEN — the nickname-collision pair refusal is branchable instead of -32000, so an embedder writes its own recovery copy rather than substring-matching ours. The prose changed with it: it named the set_nickname CONTROL VERB as the remedy, which a GUI user cannot type, and the refusal is generated inviter-side so the embedder displaying it could not rewrite it (#147); to 32 with SelfNetwork::identity_conflict_epoch — two nodes booted from COPIES of one mesh root share an endpoint id, and the displaced one’s peers went unreachable with nothing saying why. The relay reports it and iroh only warn!s it, so the fact existed and was unreadable (#134); to 33 with the peer_diagnostics verb — a long-lived pairing that cannot hole-punch while a fresh identity on the same hardware can differs only in DURABLE per-peer state, and none of it was readable from outside the daemon (#140); to 34 when outstanding invites became DURABLE — invite.expires_at_epoch changed meaning from an upper bound on the daemon’s process lifetime to the real lifetime, and invite gained an error where it previously always succeeded. No shape changed, which is exactly the class minor 10 records: guard on api_minor >= 34 before telling a user their invite will still be good tomorrow (#87b); to 35 with InviteParams.max_uses + InviteResult.uses_remaining — a bounded multi-use invite, so onboarding a team is one link rather than one ceremony per person. Each redemption still runs its own SAS and writes its own peer rows; it is N pairings sharing a secret, never a group identity (#87); to 36 with branchable codes for the rest of the ONBOARDING refusals — expired line, no live invite, inviter unreachable, id mismatch, name conflict, and the deliberately-opaque refusal. ERR_NICKNAME_TAKEN had been the only coded pairing failure, so every other one arrived as -32000 and an embedder could either forward our prose to end users or substring-match it (#159); to 43 with InviteParams::as_self — SELF-ENROLLMENT, so one person’s devices share a user_id instead of appearing as unrelated strangers (#86). The ceremony is ordinary pairing; the outcome is a device→user binding rather than a peer row, and the private key never moves. Guard on >= 43; to 42 with the peer_introduce + peer_endorse verbs — install a peer from a SIGNED endorsement by someone you are already paired with, so a small group onboards in O(N) instead of O(N²) two-human ceremonies (#65). It installs IDENTITY only and grants nothing, which is what bounds it. Guard on >= 42; to 41 with StreamFrame::BlobTransfer — live app-blob transfer progress on both the serving and fetching side (#82 ask 2), so an embedder can draw a real progress bar instead of an indeterminate spinner. Guard on >= 41 before expecting the frame. NOTE what it does NOT bring: blob_fetch still blocks its whole control connection for the transfer, and cancellation still does not exist (#172) — progress arrives on the SUBSCRIBE connection, which is a different one; to 40 with [services.<name>].rate_limit_per_min + RegisterServiceParams::rate_limit_per_min — proxied-request buckets became per (service, endpoint) instead of one shared per-endpoint bucket, so a noisy service can no longer starve a quiet one (#63). -32053 changes meaning with it: it is now per-service, so a consumer that backs off globally on one is backing off further than it needs to. Guard on >= 40 before sending the field or narrowing a back-off; to 39 with PairParams::as_nickname + InviteParams::peer_nickname — LOCAL aliases for the other party, so a nickname collision is resolvable by the person who hit it instead of requiring the other human to rename a machine or re-mint. #147 made the collision diagnosable; this makes it fixable. Guard on >= 39 before offering an alias field in a UI: below it deny_unknown_fields rejects the whole request (#87); to 38 with [network].presence_mode + SelfNetwork. presence_modereachable: false gained a new meaning (“up, paired, and deliberately not answering”), and peer_services flips from “reachable, empty list” to “unreachable” for a caller holding no grant. A consumer must guard on api_minor >= 38 before telling a user their peer is offline, since below it that verdict could not mean this (#89); to 37 when the reserved mcpmesh/* _meta namespace began being enforced on EVERY proxied frame rather than the session’s first. run_session treats frame 1 as the initialize whatever its method is, so a caller could send any other method first and put its real initialize — with a forged mcpmesh/peer naming another principal, forged groups and all — in frame 2, where nothing stripped or injected. No shape changed; what changed is whether _meta["mcpmesh/peer"] can be trusted, which is the entire reason a backend reads it. Guard on api_minor >= 37 before keying authorization on that value (#164).

Not every semantic change gets a minor, and that is the gap to watch (#122). A minor marks a change to this surface. A change to behaviour BEHIND the surface — same fields, same shapes, different meaning — may not bump it, and is invisible to a type diff. 17 and 24 above happen to be that class and did bump; do not infer from them that every such change will. When bumping several minors at once, read this block end to end AND the release notes, not the diff.

That class is bigger than it looks: 10, 17, 21, 22, 23, 24 and 37 all shipped with no change to any type in this file — they moved meaning, not shape. Seven of the forty, and 37 is a SECURITY fix, which is the case where a consumer most needs the guard. 38 adds a field, but its REAL content is a meaning change to reachable — the field exists so the new meaning is observable at all. A downstream that diffs types across a multi-minor bump sees nothing for any of them.