From ff86ff059f308f8f75e4d7bf51b0cf887dd8acc7 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sat, 19 Sep 2026 22:59:11 -0400 Subject: [PATCH] The simulator writes its own history, not yours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--mock` is a fake radio, not a fake MeshTerm: what it adverted was recorded to `meshterm.db` like anything a real companion said, so its four invented contacts turned up in the mesh walk, the dashboard and the map of the mesh actually being run. A `--mock` run that names no database now records to `/meshterm-mock.db`, created on first use. `--db` still wins — where a run records and which radio it talks to are different questions, so an explicit path is a precedence rule and not the usage error `--mock --port` is. `$MESHTERM_HOME` remains the only thing that isolates a run whole: the contact and channel caches, the outbox and the remembered devices are untouched by this, and the docs say so where they used to warn that `--mock` recorded into the real history. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 10 ++++++++ docs/cli.md | 20 +++++++++------- docs/configuration.md | 4 +++- meshterm/cli.py | 20 +++++++++++++--- meshterm/core/config.py | 13 ++++++++++- tests/test_cli_contract.py | 47 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e80612..a7cfb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,16 @@ one caveat SemVer makes for a leading zero: while the major version is still `0` ## [Unreleased] +### Changed + +- **`--mock` keeps its invented mesh out of your history.** The simulator is a fake radio, + not a fake MeshTerm: what it adverted was written to `meshterm.db` like anything a real + companion said, and its four invented contacts then turned up in the mesh walk, the + dashboard and the map of the mesh you actually run. A `--mock` run now records to + `meshterm-mock.db` beside it, created on first use. Naming a database with `--db` still + wins, and `$MESHTERM_HOME` is still the only thing that isolates a run whole — the + contact and channel caches, the outbox and the remembered devices are untouched by this. + ## [0.9.0] — 2026-09-22 **The first public release.** Everything in MeshTerm is new today, so instead of a list of diff --git a/docs/cli.md b/docs/cli.md index 730b3ed..d1db5f3 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -57,8 +57,8 @@ With no command, MeshTerm launches the interactive menu instead. | `--ble ADDRESS` | Bluetooth address of a companion; selects the BLE transport. | | `--ble-pin PIN` | Pairing PIN, if the Bluetooth companion asks for one. | | `--tcp HOST[:PORT]` | Network address of a TCP companion; selects the TCP transport. Default port 5000. | -| `--mock` | Use the built-in simulator instead of real hardware. Nothing transmits. | -| `--db PATH` | Use this SQLite database instead of `~/.meshterm/meshterm.db`. **It moves the database and nothing else** — see [Where MeshTerm keeps its state](#where-meshterm-keeps-its-state). | +| `--mock` | Use the built-in simulator instead of real hardware. Nothing transmits, and the run records to `meshterm-mock.db` rather than your real history. | +| `--db PATH` | Use this SQLite database instead of `~/.meshterm/meshterm.db` (or `meshterm-mock.db`, under `--mock`). **It moves the database and nothing else** — see [Where MeshTerm keeps its state](#where-meshterm-keeps-its-state). | | `--json` | Print the answer as JSON instead of aligned text. | | `--absolute` | Print times as ISO-8601 instants rather than relative ages, for this run. | | `-q`, `--quiet` | Suppress console logging entirely (the log file still records). | @@ -97,6 +97,7 @@ of two directories from the same shell. | File | What it holds | | --- | --- | | `meshterm.db` | The history: every packet overheard, every trace walked, every message. **The one file `--db` moves.** | +| `meshterm-mock.db` | The same, for the simulator: where a `--mock` run records when it doesn't name a database itself. Absent until you run one. | | `config.toml` | Machine setup — device profiles, where the database lives. Yours to write; MeshTerm only reads it. | | `preferences.toml` | Your overrides of MeshTerm's own behaviour. Lists only what you changed. | | `courier.json` | The outbox — queued messages, waiting and finished. | @@ -114,7 +115,8 @@ weight but none of the identity: the contact and channel caches, the outbox, the admin passwords and the remembered device all live beside it in the config directory and would still be the ones you use every day. To run against a scratch state — a test, a demo, a second radio — set `MESHTERM_HOME`, and set `--db` inside it if you want the -database somewhere else again. +database somewhere else again. The same holds for the database `--mock` picks for itself: +it keeps the simulator's invented mesh out of your history, not out of the caches. --- @@ -140,11 +142,13 @@ Three things the simulator does that a radio does not, all visible in the sample *forced* path (`--path "a1,d4"`) resolves properly. The forced form is what the samples use, and what to build a golden file from. -> **`--mock` records into your real history unless you tell it not to.** The simulator is -> a fake radio, not a fake MeshTerm: what it adverts is written to the database and the -> caches like anything else, and invented nodes then turn up in the mesh walk, the -> dashboard and the map. Give it its own home — `MESHTERM_HOME=... meshterm --mock …` — -> or its own database with `--db`, and it leaves your mesh alone. +> **`--mock` records into its own history, not yours — but it still writes the caches.** +> The simulator is a fake radio, not a fake MeshTerm: what it adverts is written down like +> anything else. The database it writes is `meshterm-mock.db`, so its invented nodes stay +> out of your mesh walk, dashboard and map; naming a database with `--db` overrides that +> choice. The contact and channel caches, the outbox and the remembered devices are still +> the ones you use every day, so a demo that should touch nothing at all gets a home of +> its own: `MESHTERM_HOME=... meshterm --mock …`. --- diff --git a/docs/configuration.md b/docs/configuration.md index a4d6cb4..bbf345a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,7 +53,9 @@ Both live in `~/.meshterm`, along with everything else MeshTerm remembers: the S history, the outbox, the contact and channel caches, stored admin passwords, and the log. **`$MESHTERM_HOME` moves the whole directory**, which is the way to run a second radio — or a `--mock` session — without touching the one you use every day. `--db` moves the -database alone; the rest stays where it was. [`docs/cli.md`](cli.md#where-meshterm-keeps-its-state) +database alone; the rest stays where it was. A `--mock` run that names neither writes to +`meshterm-mock.db` beside the real history, so the simulator's invented nodes never enter +it. [`docs/cli.md`](cli.md#where-meshterm-keeps-its-state) lists every file. Timestamps are stored as UTC and rendered in your local time. History older than the diff --git a/meshterm/cli.py b/meshterm/cli.py index 3611f09..7a57266 100644 --- a/meshterm/cli.py +++ b/meshterm/cli.py @@ -22,7 +22,7 @@ from . import __version__ from .context import AppContext from .core import exitcodes, win32dll from .core.admin_store import AdminStore -from .core.config import Settings +from .core.config import MOCK_DB_FILENAME, Settings from .core.connection import DeviceCommandError, is_connection_lost from .core.device_config import DeviceConfigError from .core.device_store import DeviceStore @@ -175,7 +175,9 @@ def main_callback( help="Network address host[:port] of a TCP companion (selects the TCP transport)", ), mock: bool = typer.Option(False, "--mock", help="Use the built-in simulator"), - db_path: Path | None = typer.Option(None, "--db", help="SQLite database path"), + db_path: Path | None = typer.Option( + None, "--db", help="SQLite database path (--mock records to meshterm-mock.db)" + ), json_output: bool = typer.Option(False, "--json", help="Machine-readable output"), absolute: bool = typer.Option( False, "--absolute", help="Print absolute timestamps instead of relative ages" @@ -198,7 +200,8 @@ def main_callback( ble_pin: Optional BLE pairing PIN for the Bluetooth companion. tcp: Explicit network address ``host[:port]``, selecting the TCP transport. mock: Whether to use the simulator instead of real hardware. - db_path: Override the database location. + db_path: Override the database location. Without it, a ``--mock`` run records to + the simulator's own database rather than the real history. json_output: Print the answer as JSON instead of aligned text. absolute: Print times as ISO-8601 instants rather than relative ages, for this run. quiet: Suppress console logging (file logging continues). @@ -232,6 +235,17 @@ def main_callback( settings = Settings.load() if db_path is not None: settings.db_path = db_path + elif mock: + # The simulator is a fake radio, not a fake MeshTerm: everything it adverts was + # written to the real history like anything a companion said, and its four + # invented contacts then turned up in the mesh walk, the dashboard and the map of + # the mesh you actually run. A pretend radio gets a pretend history — + # ``/meshterm-mock.db``, created on first use — while ``--db`` still + # wins, because naming a database is a deliberate claim about where a run records. + # ``$MESHTERM_HOME`` remains the only thing that isolates a run whole: the contact + # and channel caches, the outbox and the remembered devices live beside the + # database either way (see docs/cli.md). + settings.db_path = settings.config_dir / MOCK_DB_FILENAME # Two front ends, two consoles, and which one this process gets is settled here — a # named subcommand is a scripted run, so it prints on the plain, colourless, diff --git a/meshterm/core/config.py b/meshterm/core/config.py index a420997..91be0fd 100644 --- a/meshterm/core/config.py +++ b/meshterm/core/config.py @@ -34,6 +34,17 @@ else: # pragma: no cover - exercised only on 3.10 #: can move it between cases. CONFIG_DIR_ENV = "MESHTERM_HOME" +#: The history's filename inside the config directory, when ``config.toml`` and ``--db`` +#: both leave it alone. +DB_FILENAME = "meshterm.db" + +#: The simulator's history, kept beside the real one and used by ``--mock`` runs that did +#: not name a database themselves. ``--mock`` is a fake radio, not a fake MeshTerm: what +#: the simulator adverts is recorded like anything a real companion said, so without a +#: database of its own its four invented contacts turn up in the mesh walk, the dashboard +#: and the map of the mesh you actually run. +MOCK_DB_FILENAME = "meshterm-mock.db" + def default_config_dir() -> Path: """Return the directory MeshTerm uses for config and data. @@ -138,7 +149,7 @@ class Settings: def __post_init__(self) -> None: """Derive dependent paths that were not explicitly provided.""" if self.db_path is None: - self.db_path = self.config_dir / "meshterm.db" + self.db_path = self.config_dir / DB_FILENAME def resolve_profile(self, name: str | None) -> DeviceProfile | None: """Look up a profile by name, falling back to the default profile. diff --git a/tests/test_cli_contract.py b/tests/test_cli_contract.py index 6373878..3d054f9 100644 --- a/tests/test_cli_contract.py +++ b/tests/test_cli_contract.py @@ -707,6 +707,53 @@ def test_a_closing_message_goes_to_stderr_too(run) -> None: # noqa: ANN001 assert "records stored" in result.stderr +# -- the simulator's own history ------------------------------------------------------ + + +def test_mock_records_into_a_database_of_its_own( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """A pretend radio gets a pretend history, so a demo run leaves the real mesh alone. + + The simulator is a fake radio, not a fake MeshTerm: what it adverts used to be written + to ``meshterm.db`` like anything a companion said, and its invented contacts then sat + in the mesh walk, the dashboard and the map of the mesh actually being run. The run + still records — the history is how a ``--mock`` session's own tools read back what it + heard — just not there. + """ + from meshterm.cli import app + + home = tmp_path / "home" + monkeypatch.setenv("MESHTERM_HOME", str(home)) + + result = CliRunner().invoke(app, ["--mock", "info"]) + assert result.exit_code == exitcodes.OK, result.output + assert (home / "meshterm-mock.db").exists() + assert not (home / "meshterm.db").exists() + + +def test_an_explicit_db_still_wins_under_mock( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """``--db`` names one exact database, which is a claim the simulator does not override. + + Unlike ``--port``, it is not a contradictory one: where a run records and which radio + it talks to are different questions, so this is a precedence rule and not a usage + error. It is also what the suite's own ``run`` fixture depends on. + """ + from meshterm.cli import app + + home = tmp_path / "home" + monkeypatch.setenv("MESHTERM_HOME", str(home)) + named = tmp_path / "named.db" + + result = CliRunner().invoke(app, ["--mock", "--db", str(named), "info"]) + assert result.exit_code == exitcodes.OK, result.output + assert named.exists() + assert not (home / "meshterm-mock.db").exists() + assert not (home / "meshterm.db").exists() + + # -- devices under --mock -------------------------------------------------------------