Skip to main content

Safety

BHTune's MRFT test switches a real loop to manual and strokes its MV — the same as the legacy tool, but with one important difference: the legacy tool assumed an operator was always watching and could hit Stop. BHTune adds real guardrails for scheduled and scripted tunes that run with nobody present. This page explains exactly what those guardrails do, since "what happens if I press Ctrl+C" and "what happens if the network hiccups mid-test" both matter before you point BHTune at a real process.

Cancellation​

Pressing Ctrl+C during a bhtune tune/bhtune simulate run (or clicking Cancel on the web GUI's run detail screen, which triggers the same code path) requests cancellation and best-effort restoration. It does not guarantee that the controller has been restored:

  • First Ctrl+C stops polling immediately and starts the restore (see Restoration below). It works no matter when it's pressed — including mid-read or mid-write to a stalled driver, not just while idle between poll ticks. Every in-flight driver call is bounded by the global [tuning].op_timeout_secs setting (default 30s), so a stalled OPC DA read or write is abandoned rather than waited on forever, which is what makes cancellation reliable even against a wedged gateway or a black-holed network.
  • Second Ctrl+C, pressed while the restore itself is still running, forces an immediate hard stop instead of waiting any longer for the restore to finish. BHTune prints exactly which MV tag it was restoring and what value it was last written to, so you can put it back by hand. This exits with a distinct code (6, see the exit code table below) rather than the normal abort code, because "aborted and restored" and "aborted, restore abandoned — go check the loop" are different situations for whoever (or whatever scheduler) is watching the exit code.
  • [tuning].timeout_secs (default 3600) is the overall wall-clock budget for the whole test. It fires the same restore path as Ctrl+C — including working correctly mid-hung-read — and is meant as the backstop for scheduled/unattended runs where nobody is present to press Ctrl+C at all.
  • [tuning].restore_timeout_secs (default 30) is the initial budget for the restore step, independent of [tuning].timeout_secs — a restore triggered by a timeout doesn't inherit an already-expired budget. If the authoritative MV restore write is accepted near that deadline, BHTune extends the effective deadline as needed to preserve the complete four-second MV confirmation window; the remaining restore steps use that same effective deadline.

These five values are global installation settings in the browser's Configuration page or the [tuning] section of bhtune.toml; they apply to future runs only. Try it yourself: set a long [tuning].poll_interval_ms, start a run, and press Ctrl+C while it's waiting between ticks (works immediately); then point it at an unreachable --bridge-host and press Ctrl+C — it should abort and report within [tuning].op_timeout_secs, not hang.

Global tune timing settings​

The [tuning] section controls the operational timing and safety limits shared by all new tunes:

SettingDefaultPurpose
mrft_delay_secs0 sPre/post-test recording padding
poll_interval_ms800 msDelay between driver polls
timeout_secs3600 sWhole-run wall-clock limit
op_timeout_secs30 sLimit for one driver read or write
restore_timeout_secs30 sInitial restore budget; an accepted MV restore may extend it for confirmation

Values must be valid before a tune can touch the loop. mrft_delay_secs accepts 0 through 3600; the other settings must be positive whole numbers. OPC DA preparation additionally requires restore_timeout_secs of at least four seconds because accepted MV commands have a four-second actuation-confirmation window. Simulator runs may use a shorter positive restore timeout. Configuration changes affect future tune preparations only; an already-prepared or running tune keeps its captured values.

Public Demo mode​

Demo mode is a separate, simulator-only server surface. Its 200 ms poll interval, 30-second run timeout, request limits, quotas, session lifetime, and history caps are fixed application-owned safeguards. The optional [demo] configuration table can declare those values for deployment validation, but it cannot widen or otherwise override them. Demo runs use the stable Simulator demo identity and never connect to OPC DA or write PID constants. See the public simulator demo guide for the complete boundary and self-hosting requirements.

Timing and host responsiveness​

Live OPC DA runs measure MRFT time with a monotonic clock paired to the run's UTC start timestamp. The timestamps stored in history remain ordinary UTC values, but their progression comes from monotonic elapsed time. An NTP correction or manual system-clock adjustment after the run starts therefore cannot shorten, lengthen, reverse, or skip an apparent relay period.

Real delays are not hidden. If the operating system schedules BHTune late, the gateway responds slowly, or an OPC read/write takes longer than expected, that elapsed time remains part of the sample and switch timeline. The polling loop delays its next schedule instead of issuing a burst of catch-up reads or writes against a live controller.

BHTune is not a hard-real-time controller and cannot guarantee identical live samples on an overloaded host. Keep the BHTune host and OPC DA gateway responsive, avoid competing heavy work during a tune, and choose a poll interval comfortably shorter than the loop's expected oscillation period. The whole-run and per-operation safety timeouts remain independent monotonic timers.

Each run with at least one successful PV poll stores a timing snapshot in history. The CLI's bhtune history show <run> output, the run-detail API, and structured logs retain the requested interval, observed sample-gap count, mean and maximum sample gap, measured oscillation period when the test completed, and approximate samples per period. The normal web run-detail page intentionally omits these low-level diagnostics so it can focus on actionable run and safety information. A live run is flagged in structured logs when an adjacent sample gap is at least twice the requested interval, because that objectively means at least one complete polling opportunity was missed. This is a warning, not a validity verdict: it does not abort the run, change its calculated constants, or prevent an engineer from applying them.

The recorded timing data also retains sampling adequacy for CLI history and the run-detail API. adequate means at least six observed samples per measured oscillation period; marginal means fewer than six; and not_assessed means no usable finite period was available. This is an advisory signal, not an automatic rejection: a valid result with marginal sampling remains writable, but should be reviewed against the trend and the recorded timing data before it is applied. The normal web page does not display this advisory as a tuning-quality badge; invalid results and actual quality, actuation, and restoration failures remain visible.

Live Auto-release settling​

When a live OPC DA tune starts with the loop in the template-defined Auto value and the MRFT completes normally, BHTune does not release the loop to Auto immediately. It first restores and verifies the original MV, keeps the loop in Manual for one-third of the measured oscillation period, and polls the PV at the configured interval during that hold. Those PV samples are persisted as ordinary run samples, so they appear in the trend, history, API responses, and exports. The MRFT state remains frozen during the hold: no additional relay decisions or MV commands are issued.

When the initial raw mode matches the template's Auto value and a setpoint tag is configured, BHTune reads the original setpoint while the loop is still in Auto, before any mode or MV write. It persists that value with the run's initial readings before beginning the mode transition. The captured value is the restore target even if BHTune did not change the setpoint during the test. A non-Auto starting mode (for example, MAN or CAS) does not cause a setpoint read or setpoint write-back; the original controller mode is handled separately according to the template's revert_mode setting.

After the hold, the final restore sequence writes the template-defined Auto value first, then attempts to write back the captured setpoint, followed by the original mode-attribute value. The setpoint is therefore not restored before the controller is switched to Auto. These steps are best-effort and are not atomic: if the Auto-mode write succeeds but the later setpoint or mode-attribute write fails, or the restore is interrupted by its deadline or a second Ctrl+C, the controller may already be in Auto with some original values unrestored. The run reports an incomplete restore; check the actual mode, setpoint, and mode attribute at the DCS/PLC before another tune.

If the settling hold itself fails before final mode restoration begins, BHTune skips its Auto-release write and leaves the mode as it was at that point (normally Manual). It still attempts the remaining applicable restore steps, subject to the remaining restore-time budget, and reports an incomplete restore for operator follow-up. Simulator/Demo runs, non-Auto starts, and runs without a valid measured period do not use the settling hold. The hold shares the configured restore-time budget and never extends it.

The timing snapshot includes successful PV-read, MV-write, MV-verification-read, sample-persistence, and total-tick-work latency summaries in addition to sample gaps. When a pending relay is checked by the normal batched PV/MV poll, the same underlying OPC operation may contribute to both the PV-read and MV-verification categories; those categories describe overlapping evidence and must not be added together as independent network durations. Failed, cancelled, and timed-out operations are excluded from these successful-latency measurements.

Input validation​

Every number that reaches the tuning engine is validated before any live I/O happens: relay amplitude, cycle counts (a zero cycle count is rejected outright rather than reaching the engine and panicking mid-test, which is what the earliest builds did), PV/MV ranges (must be finite and correctly ordered — a NaN or an inverted range is rejected, not silently propagated into a PID write), and the initial MV must fall inside the validated MV range. Command-line flags reject non-finite/out-of-range input immediately with a clear message; anything read from the driver (a real DCS/PLC's current ranges, for instance) is validated again right after being read, before the loop is ever switched to manual. An effective relay step below the minimum that can be distinguished safely at f32 precision is rejected at this same pre-mutation boundary.

Read-only tune preflight​

bhtune check accepts the same tune inputs as bhtune tune and checks them before an MRFT is started. For OPC DA it performs bounded gateway discovery and reads every derived tag once, including PID constant tags. It reports values and OPC quality, validates template mode values, and checks the initial PV/MV state and requested write-back readiness.

Full mode offers the same check from the New Tune page's Check readiness action. The browser sends the same tune-request fields to POST /api/runs/preflight and displays the overall pass/warn/fail result, individual checks, and tag reads. The route uses a read-only database connection and read-only driver; it does not create run history, start a tune, or write controller values. Demo mode does not mount the route or show the action.

The CLI command does not create or update run history, run a tune, change controller values, or probe write permissions. It uses a read-only database connection when saved templates are needed and a driver wrapper that rejects writes. When a template defines a mode-attribute program value, the check reads that value as another write-back prerequisite but never changes it. A passing write-back check confirms only that the P/I/D tags are configured and readable as numeric values and any template-specific prerequisites are met; it cannot prove a future write will be accepted or that an accepted MV write will move the plant. The GUI report has the same limitation. Follow the live-plant procedures below before running a tune.

--output json emits the full report. Exit code 0 means the checks passed, 1 means the preflight could not run, and 8 means a check failed or --strict treated a warning as a failure. Without --strict, warnings are reported but do not prevent a successful exit.

Invalid calculated results​

Every response-level result is checked before it is stored as usable tuning data. A non-positive or non-finite PV amplitude or period, or any non-finite intermediate or template-converted PID value, is stored as Invalid with an explicit diagnostic reason and without numeric tuning values. Invalid calculated-result rows cannot be selected for PID write-back in the CLI or web GUI. This backstop remains important even when sampling is adequate, because a degenerate measurement can arise from a future algorithm or data-path defect.

Template precision is a separate write-readiness check. An otherwise valid result remains unwritable if rounding an active P/I/D term to the template's final units makes it zero or non-finite. The CLI and browser report the reason before PID I/O; raw calculations and their validity remain unchanged. Increase the template precision and start a new run rather than overriding the recorded run's policy.

MV actuation verification​

Every accepted OPC DA relay write is read back before a later relay command can replace it. While that command is pending, the normal poll requests PV and MV in one deduplicated batch and evaluates the MV observation before the PV sample is persisted or the MRFT engine advances. This makes the normal poll the primary verification path without weakening the safety rule. If normal polling does not provide usable evidence before the deadline, a separately bounded MV-only read remains available as a fallback.

The first check occurs at the earlier of the MRFT noise-protection boundary and four seconds after write acceptance. An early mismatch remains pending and is retried; a mismatch at four seconds, or when the engine genuinely needs the next relay command, aborts the run without writing the replacement.

The absolute tolerance combines the f32 precision floor with 0.1% of the configured MV span. For relay commands it is capped at 25% of the actual step, preventing a wide range from making a small command appear confirmed accidentally. Restore confirmation uses the same precision/span tolerance without the relay cap. The final MRFT snapback hands responsibility to the authoritative restore write, so BHTune does not wait twice for the same original-MV target. The restore readback is attempted immediately; only a mismatch is retried. A restore_timeout_secs value below four seconds is rejected during OPC DA preparation, before a live loop is mutated. This is the initial budget: when an authoritative MV restore write is accepted close to its end, the effective deadline extends to at least four seconds after that acceptance so the required confirmation window cannot be cut short. Remaining mode and setpoint restore steps share the extended deadline. An MV read that starts before the deadline but returns after it is still treated as late and does not confirm the command. The fresh read started at the deadline is independently capped at one second, rather than inheriting the full per-operation timeout, so a stalled read cannot hold the tune open indefinitely.

The MV observation is evidence about physical actuation, not a second trend point: it does not advance MRFT time or add a separate trend/export sample. A batched PV/MV request does, however, record the successful operation in both the PV-read and MV-verification latency categories; those categories overlap because they describe one request. bhtune history show <run> records each accepted command, observation, tolerance, deadline, and final status.

Gateway compatibility​

Before an OPC DA tune switches a loop to manual, and before a PID write or revert touches the loop, BHTune asks the opcda-bridge gateway which protocol versions it supports. The result is stored with the run and shown by bhtune history show, bhtune opc gateway-info, and the run detail page.

  • Full means the gateway covers every protocol version this BHTune build requires. The operation proceeds with no compatibility warning.
  • Partial means the core protocol overlaps, but an optional feature such as namespace browse or indexed search does not. The tune, write, or revert proceeds. Affected inspection features are degraded, and BHTune logs a warning.
  • Unknown means the gateway did not report a protocol range. Live operations proceed with a warning. Discovery, browse, and read stay available and report the unverified result instead of failing the request.
  • Incompatible means the core protocol ranges do not overlap. BHTune refuses the tune, write, or revert before changing the loop and tells you to upgrade the gateway or BHTune so the core ranges overlap. A refused tune is a setup failure (exit code 1) and does not create a running run. The web GUI returns the same refusal as an error and writes nothing.

Read-only OPC discovery, browse, and tag reads are not refused for an incompatible gateway. They return the compatibility result so you can see what the gateway cannot do. See OPC gateway compatibility for the checks and where the snapshot is shown.

OPC quality​

Every OPC DA read reports a quality alongside its value (Good/Uncertain/Bad). BHTune always accepts Good, accepts Uncertain by default, and never accepts Bad for tuning-critical operations:

  • Before the loop is touched (initial PV/MV/range/mode/direction reads): a Bad or policy-rejected Uncertain reading is a hard failure. Nothing has been mutated yet, so this is a clean refusal.
  • During the test (every polled PV sample): a Bad or policy-rejected Uncertain sample aborts the run and attempts to restore the loop. A held or stale PV during a relay half-cycle would corrupt the exact period measurement the test depends on — silently tolerating it is worse than aborting loudly. The triggering sample is still recorded (with its real quality) before the abort.
  • During MV actuation verification: an unacceptable readback is recorded but does not immediately prove failure. Confirmation remains pending and is retried until the four-second deadline; if the engine needs the next relay command first, the run aborts without issuing that replacement.
  • During write-back confirmation: a Bad or policy-rejected Uncertain readback is treated as an unconfirmed write, which triggers rollback (see PID write-back below).
  • When selecting a tag in the web browser: BHTune re-reads the exact item selected in the tree before applying the template's PV suffix. Good quality proceeds normally; Uncertain or Bad quality requires an explicit choice to select another tag or proceed anyway. This only accepts the item into the form; tune execution still enforces the quality rules above.

Bad quality is never accepted under any setting. Sites whose gateway reports Uncertain as a matter of course can leave the default allow_uncertain_quality = true, or disable that global policy on the Config page / in bhtune.toml when uncertain readings must be rejected — enabled by default, logged loudly every time it changes the outcome, and recorded on the run so history shows which runs executed under relaxed rules.

Restoration​

BHTune guarantees a best-effort restore on every exit path — successful completion, an error partway through, Ctrl+C, or a timeout — not just the happy path. Before the first live write, BHTune captures and persists the initial mode and mode-attribute values and, only when the raw starting mode is the template-defined Auto value and a setpoint tag is configured, the initial setpoint. The mutation guard separately records which mode-transition writes were attempted. The original MV restore is always attempted; the other restore steps depend on the captured values, transition state, and template settings.

In the normal release-to-initial path, restore proceeds in this order: original MV, original controller mode when applicable, captured Auto-start setpoint when applicable, and original mode-attribute value when applicable. A keep-Manual path omits the controller-mode restore. Restore writes are attempted independently, so one failed step does not by itself prevent later steps from being tried; a restore deadline or second Ctrl+C can interrupt the sequence.

bhtune history show <run-id> (or the run detail screen) reports the restore outcome as confirmed or incomplete, with the failed step details. For live OPC DA, the original MV has a separate readback-confirmation step; controller-mode, setpoint, and mode-attribute restoration record write results but do not have their own readback verification. An incomplete restore does not guarantee that the controller is still in Manual: in the return-to-Auto path, Auto is written before the setpoint and mode attribute, so one of those later steps can fail after Auto has already been requested. Inspect the actual DCS/PLC values before another tune. An incomplete restore exits with code 6, distinct from a normal abort.

Preparation records the run before writing its effective timing, quality policy, connection, and other provenance metadata. If one of those follow-up writes fails, BHTune immediately marks the row failed instead of leaving it permanently running. If the terminal update itself cannot be persisted, the row is removed as a fallback and the failure is logged.

Recovering an interrupted live tune​

Full-mode startup checks for abandoned live-operation owners but never connects to a controller or attempts a restore. Each live operation holds an OS-level database lock and a conditional database claim. A heartbeat older than 30 seconds only makes an owner a candidate: if the process is paused and still holds the lock, startup cannot take ownership and does not recover the loop. Before changing orphan state, startup exports the run, owner, and mutation evidence next to the database in <database-file>.recovery/.

Only an owned, still-running OPC tune with complete and consistent recorded connection, resource identity, initial readings, template policy, timing, restore intent, and mutation audit becomes explicitly eligible. An Auto-start run with a configured setpoint restore target must have its initial setpoint recorded. Legacy rows and incomplete or mismatched evidence fail closed. Review the exported evidence and run detail before an intentional live recovery:

bhtune restore-loop <run-id> --yes --output table

Use --output json for a machine-readable report. Optional --bridge-host and --server arguments only verify the recorded connection; they cannot redirect recovery. BHTune reuses the original run's connection and restore policy, confirms the MV under the normal quality and four-second accepted-write deadline, and reports the outcome of every permitted restore step. Exit 0 means the recovery and its audit were confirmed. Exit 6 means recovery was incomplete or its required final audit was not persisted; inspect the per-step report and the actual loop before taking further action. When MV confirmation fails, BHTune can intentionally keep the loop in Manual. If a row is not eligible, do not bypass the guard: use the exported evidence and the site's manual recovery procedure. Demo mode does not run this live recovery sweep.

PID write-back​

Omitting --write-pid does not make a live MRFT read-only: it still changes mode and commands MV relay steps. Use bhtune check or Full mode's Check readiness for a read-only preflight. The CLI requires --yes with --write-pid <level> on every tune path, including interactive use; that flag confirms unattended PID write-back. The Web UI requires its explicit review/confirmation. Completed OPC runs can also review/write results after the tune. BHTune:

  1. Reads and persists the current P/I/D values first, before writing anything. If this pre-read fails, nothing is written at all.
  2. Writes and verifies each constant individually (P, then I, then D), checking the readback against what was requested within a small tolerance — a DCS's own unit rounding means a just-written value isn't always bit-identical on readback, so exact equality would produce false failures.
  3. Rolls back only what was actually confirmed if any constant fails partway through — if P succeeds and I fails, only P is rolled back (D, never attempted, needs nothing; I, never confirmed, has nothing to put back).
  4. Writes D explicitly for PI controllers. A PI result writes its calculated integral value and D = 0.0, clearing any stale derivative action in the controller. A P-only result uses the template-specific integral-disabling sentinel; a full PID result writes its calculated derivative value.
  5. Records every outcome, including which case applies: nothing written, everything written and confirmed, a partial write successfully rolled back, or — the case that needs a human — a partial write whose rollback itself failed. That last case prints a message pointing at bhtune history revert <run-id>, which writes the persisted previous values back under the same pre-read/verify contract, so a write-back that turns out wrong can be undone later without anyone having written the old numbers down by hand.

New PID writes use the template policy snapshotted on the run: one decimal place for Yokogawa, three significant digits for the other built-ins. Reviews and writes agree. Inactive integral/derivative disable values remain exact. Pre-write values, actual readbacks, rollback, PID revert, and loop recovery are never quantized by that policy. See PID precision for custom-template settings.

Network exposure​

bhtune-server binds 127.0.0.1 (localhost only) by default and ships with no authentication — anyone who can reach the port can start, cancel, or configure a tune. Binding to any other address (BHTUNE_BIND=0.0.0.0:8787 or a LAN IP) is an explicit choice you make yourself; there is no installer-driven firewall rule or prompt that does this for you. Until authentication ships (a planned, not yet available, feature), treat a non-loopback bind the same way you'd treat any other unauthenticated service on your OT network: only do it on a trusted, isolated network, and prefer console/remote-desktop access to the host running bhtune-server over exposing it further. Full mode provides CSRF protection for browser mutations without requiring per-host setup: when no BHTUNE_ORIGIN or origin config value is present, the server accepts an Origin only when its host and effective port match the request Host. Set an explicit origin when a reverse proxy rewrites Host or when one external browser origin must be pinned. Requests without an Origin remain available for CLI/curl compatibility. This policy does not authenticate users, authorize operators, or make an unauthenticated non-loopback deployment safe to expose publicly.

The optional Windows-installer gateway has a different network contract. It runs as LocalSystem, is unauthenticated, and listens on 0.0.0.0:7600 so BHTune and approved remote clients can reach native OPC DA/DCOM on that host. Interactive installation displays this warning before a clean install selects the component; silent clean installation requires /INSTALL_GATEWAY=1. The installer never creates or modifies a Windows Firewall rule. Selecting the component is therefore not authorization to expose TCP 7600 beyond a trusted OT network.

The installer refuses to adopt an existing unowned OpcdaBridgeGateway registration, overwrite unexpected content in its managed Program Files directory, or proceed while TCP 7600 has an unexpected listener. Do not work around those checks by deleting services, terminating processes by name, or changing ownership metadata. Resolve the existing deployment explicitly and preserve its configuration/data first.

Packaging and database recovery boundaries​

The Windows NSIS installer keeps exactly one verified rollback backup under %ProgramData%\ByteHound\bhtune\ once an installer-managed database exists. A genuinely empty clean install has no prior database to protect and therefore has no rollback backup. Automatic database backup and rollback cover only the installer-managed SQLite database named by the absolute top-level db setting in the preserved configuration, including its -wal and -shm companions when present. The installer validates that path before stopping the service; it refuses malformed, relative, ambiguous, or external database paths by default.

When the optional gateway is installer-owned, both Windows services participate in one transaction. The installer stops them before replacement, snapshots the gateway executable and the complete gateway ProgramData subtree, validates the candidate service/listener/read-only smoke path, and restores both service definitions, data, and prior running/stopped states on failure. Gateway configuration, index database and SQLite sidecars, build metadata, logs, and rollback evidence remain inside this boundary even when BHTune uses an external database.

An operator who prepares an existing external database file and independently backs it up can explicitly acknowledge that boundary with /CUSTOM_DB_BACKUP_CONFIRMED=1. A missing or inaccessible external file, or a path that names a directory, fails closed before the installer stops the service; the installer does not create external database parents or files. After the preflight succeeds, the installer may replace binaries and service state, but the external database remains operator-owned: the installer does not claim to have backed it up or to be able to roll it back. A failed health/version validation, including validation of the restored service after rollback, still requires checking that database and the service manually.

The NSIS-installed BHTune service runs as NT AUTHORITY\LocalService. An external database therefore needs service-account access on both the existing database file and its parent directory, including permission to create or update SQLite's -wal and -shm sidecars. Elevated installer access is not evidence that the service can use an operator-owned path; the installer leaves those ACLs unchanged and the startup health check is the definitive validation. Grant only the minimum access required for the service, and independently back up the database before acknowledging the override. The optional gateway runs separately as LocalSystem, matching the upstream native OPC DA/DCOM service contract.

Linux packages preserve /etc/bhtune and /var/lib/bhtune across upgrades and removal; they do not silently delete operator data. Docker deployments have the same boundary through the mounted /var/lib/bhtune volume. For every packaging path, stop the service before copying or restoring a SQLite database and keep the database, -wal, and -shm files together. Package installers do not create firewall rules or widen BHTune's loopback bind. The optional Windows gateway retains its explicit all-interface bind without adding a firewall rule. Windows uninstall uses a guarded two-pass cleanup: ProgramData, gateway configuration/index/logs, installer recovery state, and operator data remain in place, while ownership metadata is removed only after the fixed Program Files tree has been independently verified absent.

The Debian, RPM, and Arch package units are package-managed variants of the same hardened systemd service and use /usr/bin/bhtune-server; the manual archive unit is separate and uses /usr/local/bin/bhtune-server. Installing any package reloads systemd metadata but does not enable or start BHTune. Operators must explicitly run sudo systemctl enable --now bhtune-server after reviewing the configuration. Package upgrades preserve the previous service state, and package removal preserves /etc/bhtune, /var/lib/bhtune, SQLite sidecars, and logs rather than treating them as disposable payload files.

Debian package dependency metadata is intentionally adaptive: cargo-deb uses depends = "$auto" and relies on dpkg-shlibdeps in a Debian-capable packaging environment. An empty Depends: field from a host that lacks that tool is an incomplete packaging result, not evidence that the runtime has no shared-library requirements. Do not install such an artifact without rebuilding it in the supported Debian environment or independently verifying its dependencies.

The Arch bhtune-bin package is generated from exact stable release inputs and is not a live source build. Its publication workflow rejects prerelease and arbitrary refs, validates every source checksum, builds as a non-root Arch user, and never enables or starts the service during package installation. Until the first stable release and manual AUR publication exist, use the workflow only for validation; do not present bhtune-bin as an available public package.

The Windows installer is not Authenticode-signed. SmartScreen may therefore warn about the publisher even when the file is intact. Verify the release checksum and, when available, the Sigstore bundle and GitHub provenance separately; those checks establish integrity and build provenance, not Windows publisher trust.

If an upgrade health check fails, leave the affected services stopped or in the installer-reported rollback state and preserve the installer log, service definitions, health response, gateway listener/process evidence, and database files. Do not repeatedly rerun an upgrade against the same live database while the failure is unexplained. For an NSIS installation, the installer reports rollback success only after the restored BHTune service answers the expected health/version check and a previously running gateway passes its bounded read-only smoke check; if either check fails, treat the rollback as incomplete, inspect the preserved ProgramData rollback directory, and restore the database plus -wal/-shm companions only while the service is stopped. For package or manual archive installs, keep the prior verified package/archive and restore the binaries as a matched pair, then start the service and verify /api/health before considering the recovery complete. The operator runbook lists the evidence to collect and the bounded recovery sequence.

The frontend development server is also unauthenticated and binds all local interfaces. Use its loopback URL on a trusted development host; other hostnames require Vite's host allowance and the exact browser origin. It proxies API requests to local bhtune-server; see development setup.

Scripting and exit codes​

--output json emits exactly one parseable JSON value on stdout, on every path — success, abort, timeout, poor quality, restore-incomplete, or write-back failure — so a scheduler never has to guard against stray prose interleaved with the object it's trying to parse. Exit codes are equally specific:

CodeMeaning
0Completed successfully
1Setup error (unknown template, bad flag combination, database/driver connection failure)
2Aborted by Ctrl+C, restore confirmed
3Test completed, but the requested PID write-back failed
4[tuning].timeout_secs elapsed before the test finished
5Bad or policy-rejected Uncertain quality aborted the run, with restore confirmed
6The post-run restore could not be confirmed — check the loop by hand
7An accepted OPC DA MV command could not be confirmed; restore was confirmed
8A preflight check failed, or check --strict rejected a warning

Exit code 6 takes precedence over 7 if the actuation failure is followed by an incomplete restore.

Next steps​

  • OPC gateway compatibility — how BHTune decides whether a gateway is safe to mutate, and where that result is recorded.
  • MRFT concepts — what the test is actually doing while these guardrails watch over it.
  • CLI quickstart — see --output json and the automation flags in context.