Skip to main content

Module availability

Module availability 

Source
Expand description

Which advisors can be asked right now, and why each one that cannot, cannot.

Three inputs, all cheap and none over the network (ADR 0032):

  • presence — an executable on PATH, a credential variable set (its value is never read). Presence is what the crate can know; it says “installed/configured”, not “answering”, and the status says which it measured.
  • the reasoning mode — offline admits only advisors that stay on the machine, ci admits none, the rest admit all; an explicit disable list removes any.
  • recorded outcomes — every consultation a session recorded carries its status. A run of transient failures opens the advisor’s circuit for a cooldown, a rate limit for its own, an authentication failure for longer; once the cooldown passes the advisor is available again and marked recovering. The circuit is therefore a pure function of the records and the clock: deterministic, replayable, and never a permanent failure nobody configured.

An empty result is an ordinary answer. A session with no advisor still reasons; it reasons locally, and says so.

§Example

One advisor through its lifecycle: present, then two timeouts open its circuit, then the cooldown passes and it is available again, marked recovering.

use majordomus_cli::reasoning::availability::{
    derive_availability, resolve_mode, AdvisorState, AdvisorStatus, Inputs, Outcome,
    Presence, FAILURE_COOLDOWN,
};
use majordomus_cli::reasoning::catalogue::{AdvisorCatalogue, References};
use majordomus_cli::reasoning::record::ConsultationStatus;

// Presence is the seam a test fakes: this "machine" has the executable `a`.
struct Installed;
impl Presence for Installed {
    fn executable(&self, name: &str) -> bool { name == "a" }
    fn variable(&self, _: &str) -> bool { false }
}
let catalogue: AdvisorCatalogue = majordomus_cli::metadata::yaml::parse_into(
    "version: 1\ncapabilities:\n  - id: code_review\n    description: x\nadvisors:\n  - id: a\n    title: A\n    transport: cli\n    adapter: a-cli\n    executable: a\n    capabilities: [code_review]\n",
).unwrap();
let refs = References::default();
let mode = resolve_mode(None, false, None);
let at = |now: i64, history: &[Outcome]| -> AdvisorState {
    derive_availability(&catalogue, &Inputs {
        refs: &refs, presence: &Installed, mode: mode.mode, disabled: &[],
        history, peers: &[], now, cooldown: None,
    })
    .remove(0)
};

assert_eq!(at(0, &[]).status, AdvisorStatus::Available);

let timeouts: Vec<Outcome> = (0..2)
    .map(|i| Outcome {
        id: format!("consultation-{i}"),
        advisor: "a".into(),
        status: ConsultationStatus::Timeout,
        at: 100 + i,
        retry_after: None,
    })
    .collect();
let open = at(102, &timeouts);
assert_eq!(open.status, AdvisorStatus::TemporarilyFailed);
assert_eq!(open.reason, "consecutive_failures");

let later = at(101 + FAILURE_COOLDOWN, &timeouts);
assert_eq!(later.status, AdvisorStatus::Available);
assert!(later.recovering);

Structs§

AdvisorCircuit
An open circuit: the consultation that opened it, the failures behind it, and when it closes. Derived from the records and the clock, never stored.
AdvisorState
One advisor as it stands now: what the catalogue declares of it, its status, the one machine word saying why, and what was probed. The row reasoning.advisors reports and the policy plans over.
Inputs
Everything availability reads besides the catalogue: presence, the mode, the disable list, the recorded outcomes, the linked peers and the clock. Holding them in one value is what keeps derive_availability pure.
LinkedPeer
A linked mesh runtime, as the mesh reports it. Those carrying the feature the catalogue’s peers block names become advisors with the id peer:<runtime>.
ModeResolution
The mode in force and what decided it, as resolve_mode returns it. A plan and a report carry the source beside the mode, so a reader learns why the mode is what it is.
Outcome
The recorded outcome of one consultation, as availability reads it: the advisor, how the consultation ended and when. The circuit is computed from these alone.
SystemPresence
The process’s own PATH and environment: an executable counts when a file of its name with an execute bit stands in a PATH directory, a variable when it is set and non-empty. A name containing / is never an executable on PATH.

Enums§

AdvisorStatus
An advisor’s standing: whether it may be asked now and, when not, which kind of reason keeps it out. Only available advisors enter a plan.
ReasoningMode
How much independent review a session may use, and where from. The policy reads it for the review budget; availability reads it to leave advisors out (ci all of them, offline every one that leaves the machine). standard is the default.

Constants§

AUTH_COOLDOWN
How long an authentication failure keeps an advisor out, seconds: a credential is not fixed by asking again, so it is not asked again soon.
COOLDOWN_ENV
The environment variable overriding every circuit cooldown, in seconds.
DISABLE_ENV
The environment variable listing advisors to leave out (comma-separated, or all).
FAILURES_TO_OPEN
Consecutive transient failures that open a circuit.
FAILURE_COOLDOWN
How long a circuit opened by transient failures stays open, seconds.
MODE_ENV
The environment variable that names the mode outright.
RATE_LIMIT_COOLDOWN
How long a rate limit is honoured when it names no retry-after, seconds.

Traits§

Presence
What the crate can observe about an advisor’s installation. The one seam tests fake: SystemPresence reads the process, and an example or a test answers from a list.

Functions§

derive_availability
Every advisor — declared ones in preference order, then linked peers by runtime — with its status. Pure over its inputs.
resolve_mode
Decide the mode: the variable, else a CI runner, else the active profile’s declared mode, else standard. An unreadable word is skipped with its source, never guessed.