Namespace Kuestenlogik.Bowire.Monitoring
Classes
- BowireMonitoringEndpointContribution
Discoverable endpoint-mount entry point for the Monitoring workbench surface (#102). Picked up by Core's BowireApiEndpoints scan via the IBowireEndpointContribution seam — splices the read-only /api/monitoring/* endpoints into the auth-gated bowireGroup.
- BowireMonitoringRailContribution
Monitoring rail contribution (#102) — the read-only workbench surface over the probe outcome ledger: live status per probe, sparkline strip, historical outcome table. Probes are authored as files and run by
bowire monitor run; the rail never mutates the ledger.
- BowireMonitoringServiceContribution
Discoverable service-registration entry point for the Monitoring workbench surface (#102). Picked up by Core's
AddBowireassembly scan via the IBowireServiceContribution seam. Registers only the outcome ledger at the default root — the read path the workbench rail needs. The probe engine (runner / scheduler) stays opt-in via AddBowireMonitoring(IServiceCollection, string?), which also wins the ledger registration when a host calls it with a custom root before the scan runs.
- ConsoleSignaler
The always-on Core signaler — writes a prominent line to a TextWriter when a probe crosses the pass↔fail line. This is the default channel the CLI registers; the network signalers (Slack / PagerDuty / OTLP) are opt-in sibling packages layered on top.
- MonitoringEndpoints
Workbench-facing read endpoints for the Monitoring rail (#102). The rail is strictly read-only: probes are authored as files and run by
bowire monitor run(or an embedded scheduler); the workbench only renders the outcome ledger — live status, per-probe sparkline, and the historical outcome table. Both endpoints read the ledger on request, so the surface stays live against a monitor process appending from another process — no cache, no push channel needed at the probe cadences involved.
- MonitoringServiceCollectionExtensions
DI wiring for Monitoring (#102). Registers the Core engine — the outcome ledger, the probe runner, and the TimeProvider-backed scheduler. Registers no ISignaler: outbound channels are opt-in and contributed by their own packages, so a host that calls only this runs probes and writes the ledger without any outbound call. The host must supply an IProbeExecutor (the recording-replay implementation).
- MonitoringTelemetry
The monitoring metrics surface (#102) — a single Meter with the two instruments the design calls for, in the shared
bowire.*namespace so #29's Grafana dashboards gain a section rather than a parallel one:bowire.monitoring.probe.duration— histogram of run latency (ms).bowire.monitoring.probe.outcome— counter, tags: probe + outcome.
Recording is dependency-free (System.Diagnostics.Metrics is in the BCL) and cheap when no listener is attached, so the engine records on every run. The OTLP export lives in the opt-in
Kuestenlogik.Bowire.Monitoring.Otlppackage; without it the instruments still record in-process (readable by the workbench surface).
- OutcomeLedger
The append-only outcome ledger (#102, Decision 2). Each probe gets a
<name>.jsonlfile under the ledger root; every run appends one JSON line. The last row is the source of truth for restart resume (when did this probe last run) and transition detection (was it passing or failing). Reading the last row is a cheap tail read; the workbench surface reads the whole file for the sparkline.
- Probe
A Monitoring probe (#102) — a saved invocation (BowireRecording) plus the three extras that turn it into a scheduled health check: a Schedule, the Assertions that must pass, and a Severity that routes the signal. This is the sentence the whole engine serves: "a probe is a recording with a schedule, assertions, and a severity."
- ProbeAssertion
A must-pass predicate over a probe's response. v1 covers the three checks a black-box health probe actually needs: an expected status, a latency budget, and a body substring. Richer predicates (JSON-path, header, regex) converge on the shared Flows expectation DSL in a follow-up; this minimal set keeps the Core engine self-contained.
- ProbeAssertionVerdict
One assertion's verdict — did it pass, and a human-readable why.
- ProbeExecutionException
Thrown by an IProbeExecutor when a probe run couldn't complete. The runner catches this (and only this) to record an Error outcome.
- ProbeExecutionResult
The raw result of running a probe's recording once, before assertions are applied. Produced by an IProbeExecutor.
- ProbeFile
Loads a probe definition file — a saved recording wrapped with the three Monitoring extras (schedule, assertions, severity):
{ "name": "payments-health", "schedule": "every 60s, 09:00-17:00 UTC, Mon-Fri", "severity": "crit", "assertions": [ { "kind": "status", "expected": "200" }, { "kind": "latencyBelowMs", "expected": "500" }, { "kind": "bodyContains", "expected": "healthy" } ], "recording": { "id": "...", "name": "...", "steps": [ ... ] } }A malformed file throws ProbeFileException with a clear reason (the CLI reports it and skips that probe — visible, non-silent).
- ProbeFileException
Thrown when a probe file can't be read or parsed.
- ProbeOutcome
One row in a probe's outcome ledger — the durable record of a single run. Serialised one-per-line into
<probe>.jsonl; the last row is the source of truth for restart resume + transition detection (Decision 2).
- ProbeRunner
Runs a probe once and records the outcome (#102). The pipeline per run: execute the recording → evaluate the assertions → append the outcome to the ledger → detect a pass↔fail transition against the ledger's previous row → fire every configured signaler on a transition. This is the single unit the scheduler invokes on each tick.
- ProbeSchedule
When a probe runs (#102, Decision 1). v1 accepts an interval (
every 60s,every 5m,every 2h) optionally bounded by a UTC time-of-day window and a weekday set (every 5m, 09:00-17:00 UTC, Mon-Fri). Anything the parser can't model returns a clear error rather than silently approximating — the operator sees "unsupported schedule" and the probe simply isn't scheduled. Full cron expressions are a follow-up.
- ProbeWindow
A UTC time-of-day window plus a weekday set. A probe only fires inside the window; outside it, the next run is deferred to the next in-window instant.
- RecordingProbeExecutor
The wire-level IProbeExecutor — replays a probe's saved BowireRecording through the same protocol plugins the workbench uses. Each step is dispatched via InvokeAsync(string, string, string, List<string>, bool, Dictionary<string, string>?, CancellationToken); the last step's status + response become the probe's result and the summed plugin-reported durations its latency. A missing plugin or a transport failure surfaces as ProbeExecutionException so the runner records an Error outcome.
- SignalEvent
The payload a signaler receives — which probe, what happened, and the outcome that triggered it.
- SignalerConfigException
Thrown by an ISignalerFactory when the
--signalargument is invalid.
- SignalerException
Thrown by an ISignaler when delivery fails. The runner catches this (and only this) so a bad channel is logged and skipped without aborting the run — a general exception (a bug in the signaler) still propagates.
- SignalerRegistry
Discovers the installed ISignalerFactory contributions and resolves a
--signal <scheme>:<arg>spec to a live ISignaler. Signaler packages are opt-in siblings; a scheme with no installed package resolves to a clear "install …" error rather than a crash. Same assembly-scan shape the protocol / CLI-command registries use.
- TimeProviderProbeScheduler
The Core scheduler: one TimeProvider-backed loop per probe. Building on TimeProvider (not
DateTime.UtcNow+Task.Delay()) makes cadence, drift, and lazy-start resume drivable from a fake clock in tests. Each iteration reads the probe's last ledger row, computes the delay to its next run via ProbeSchedule, waits, then runs it once through the ProbeRunner.
- TransitionDetector
Decides whether a probe crossed the pass↔fail line (#102). A signal fires only on a transition, never on every run, so an outage pages once and a recovery clears once. The "previous" state is read from the ledger's last row (Decision 2), so transition state survives a restart — a restart mid-outage neither re-fires "went critical" nor drops "recovered".
Interfaces
- IProbeExecutor
Executes a probe's saved recording and returns the raw response. The concrete implementation replays the recording through the invoke path; the seam keeps the scheduler + runner testable without a live target.
- IProbeScheduler
Drives probe runs on their schedule (#102, Decision 1). The default implementation is TimeProviderProbeScheduler — a hand-rolled TimeProvider loop, no Quartz. A Quartz-grade scheduler arrives as an optional sibling package contributing this interface.
- ISignaler
Fires when a probe crosses the pass↔fail line (#102). Implementations are the outbound integrations — Slack / PagerDuty / OTLP-logs — each shipping as a separate opt-in package and registered only when the operator passes the matching
--signalflag. Core Monitoring registers no signaler, so nothing leaves the host by default.
- ISignalerFactory
Builds an ISignaler from a
--signal <scheme>:<arg>spec. Each outbound channel ships as its own opt-in sibling package (…Monitoring.Slack,…Monitoring.PagerDuty,…Monitoring.Otlp) contributing one factory, so Core + the Core-adjacent Monitoring package gain no third-party dependencies. The CLI discovers factories by assembly scan and reports a clear "install the package" message when a scheme has no factory. Implementations must be zero-config (parameterless ctor) so the registry canActivator.CreateInstancethem.
Enums
- ProbeAssertionKind
The kinds of predicate v1 supports.
- ProbeResult
Outcome of a single probe run.
- ProbeSeverity
Severity of a probe's failure — maps onto the signaler's routing.
- ProbeTransition
The pass↔fail edge a run produced.