feat(mock): live scenario re-binding across gateways + mock received write verification#85
Merged
Merged
Conversation
A gateway seeds its FakeBackend scenario bindings once at startup; a separate-process `mock scenario activate` wrote only the session store, so a client on a long-lived connection kept seeing the startup scenario until the gateway restarted. Introduce IScenarioBindingRefresher, invoked by the SOCKET, HiSLIP, and VXI-11 gateways before each SCPI dispatch, to re-sync the binding from the session. Re-application is scoped to a changed scenario name so in-flight scene state is preserved; the port is no-throw and defaults to a no-op NullScenarioBindingRefresher when the Fake backend is not composed in.
The gateways take IScenarioBindingRefresher as an optional constructor dependency, so the direct-construction live-rebind tests would still pass even if the composition root failed to inject the real refresher — live re-binding would then silently no-op in the shipped CLI. Resolve each gateway from a production-like container and assert the injected refresher is the real SessionScenarioBindingRefresher, not the no-op default.
…band A client-app integration test drives the mock through its own VISA stack, so it never sends raw SCPI and cannot assert what it wrote by echoing. Add `ivicli mock writes <device> [--match <substr>] [--all] [--json]`, which reads the NDJSON traffic capture a serving gateway produced (IVICLI_CAPTURE) via the existing INdjsonTrafficReader and reports the SCPI writes the device received — last match by default, all with --all, non-zero exit when nothing matched. The reader opens the log with shared access, so a separate process can query it while the gateway is still serving. No new persistence path: MockWritesQuery sits in the Application layer over the ADR 0031 capture.
The mock writes handler is unit-tested against a fake capture, but the deployment path — a client write reaching the gateway, wrapped for capture, producing a Write event with the exact SCPI — was only covered by composed units. Drive the real SocketGatewayServer wrapped in CapturingBackendFactory over a loopback TCP client and assert the `:VOLT 24.000` write is captured as a TrafficEvent for the device.
Round out the requirement-2 documentation: add the `mock writes` verb to the PRD Phase-3 command roadmap (EN + JP) next to `mock scenario record`/`import`, and point the README "Audit-friendly" bullet at it (EN + JP) so the write-verification workflow is discoverable from the capture entry point.
`mock writes` read ambiguously (imperative verb vs. noun) and did not convey the reception angle — the point is to verify which writes actually reached the server/mock. Rename the verb to `received` and the types to MockReceivedWrites* across command, handler, query, errors, tests, and docs (README/PRD EN+JP, ADR 0031, guide). Behaviour and flags are unchanged.
…ck received` Refine the received-writes query surface: `--exact <scpi>` full-string filter (mutually exclusive with the substring `--match`, so `:VOLT` no longer also matches `:VOLT:PROT 30`), and `--count` to print the match count (exit 0 even at zero). `--json` now always emits an array — a single-element array by default, `[]` when empty — so a parser never branches on --all. Documents the run-isolation practice (fresh IVICLI_CAPTURE per run) in the guide and ADR 0031 instead of adding a time filter.
The env-var scenario activation (IVICLI_SCENARIO / IVICLI_SCENARIO_FOR) activated the scenario on the FakeBackend but never wrote the session store. The new live scenario-binding refresher treats the session as the source of truth and deactivates any running binding the session does not name, so it tore down an env-activated scenario on the first request — breaking the mock container's *IDN? roundtrip (docker smoke). Write the binding into the session at startup, mirroring `mock scenario activate`, so the refresher sees a matching binding and leaves it alone. Verified end-to-end locally: a SOCKET gateway started with IVICLI_SCENARIO now returns the scenario IDN on repeated *IDN? queries instead of deactivating.
The PRD is a living document describing the current product; ADRs are the frozen decision journal. A living doc must not depend on the journal (the pointer belongs on the concrete side), so the ADR references scattered through the PRD are dropped. The surrounding prose already states what each feature does; readers wanting the rationale reach the ADRs via their own index, not a back-link from the PRD. EN and JP updated in lockstep.
The README and the mock-instrument guide are living documents; a living document must not back-reference the ADR journal (the pointer belongs on the concrete side). Drop the inline ADR references and reword the few sentences whose only content was such a pointer, so each reads self-contained. The "Architecture Decision Records" index link is kept — a directory-level navigation entry into the journal, not a specific back-reference. README EN and JP updated in lockstep.
Code comments are living documentation. The mock-received query docstring back-referenced the ADR journal (ADR 0031) and, together with three test docstrings, pointed at a "requirement 2" label that lives only in an untracked scratch file — neither resolvable by a repo reader. Reword them to describe the behaviour directly so each comment stands on its own.
ShortArrow
marked this pull request as ready for review
July 21, 2026 23:37
Merged
ShortArrow
added a commit
that referenced
this pull request
Jul 22, 2026
Version bump and CHANGELOG for 0.2.8: live mock-scenario re-binding across the SOCKET / HiSLIP / VXI-11 gateways and `ivicli mock received` (#85), the `diagnose` -> `doctor` rename with a deprecated alias (#81), and the MIT OR Apache-2.0 dual license (#72). Internal project lock files refreshed to 0.2.8.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two mock-testing capabilities that let a client-app integration test drive a scenario-backed mock and verify what it sent, without touching real hardware. Ready for review; a follow-up release PR (0.2.8) adds the version bump + CHANGELOG (covering this, the doctor rename #81, and the dual license #72).
What this does
1 · Live scenario re-binding on a running gateway. A serving gateway seeds its
FakeBackendscenario bindings once at startup, so a separate-processmock scenario activate(which only writes the session) was invisible until restart. A newIScenarioBindingRefresher— invoked by the SOCKET, HiSLIP, and VXI-11 gateways before each SCPI dispatch — re-syncs the binding from the session. It re-applies only when the bound scenario name changes (in-flight scene state is preserved) and is no-throw.2 ·
mock received— out-of-band write verification. A client-app integration test drives the mock through its own VISA stack, so it never sends raw SCPI and cannot assert what it wrote.ivicli mock received <device> [--match <substr> | --exact <scpi>] [--all] [--count] [--json]reads theIVICLI_CAPTURENDJSON (shared-read, so it works while the gateway serves) and reports the writes the device received.--jsonis always an array; non-zero exit when nothing matched (except--count).Container-smoke fix folded in. Env-var scenario activation (
IVICLI_SCENARIO) now persists to the session, so the refresher does not tear down an env-activated scenario — which is exactly what the mock container relies on. Verified end-to-end.Design notes
mock receivedadds no new persistence path — it reuses the existing NDJSON traffic capture viaINdjsonTrafficReader.Testing
mock received: handler, CLI render, and real-adapter round-trip; exercised on the real binary.*IDN?over a SOCKET gateway keeps returning the scenario IDN).Category!=Integration); csharpier clean.Out of scope / follow-up
mock receivedneeds the gateway started withIVICLI_CAPTURE=<path>; use a fresh path per run (documented — no time filter by design).