8624e21372
The .NET and Java stream-events commands handed the raw ReplayGap sentinel
MxEvent to their protobuf JSON formatters (Java text mode printed
'0 MX_EVENT_FAMILY_UNSPECIFIED'), while the Go/Python/Rust CLIs already emit
the typed row (CLI-35/36). Both now branch on the sentinel: Java text mode
prints 'REPLAY_GAP requested_after=<n> oldest_available=<n>' and JSON mode a
hand-built {"replayGap":{...}} line via nextItem()/isReplayGap(); the .NET
CLI emits the same hand-built row in jsonl/text (its text mode is
JSON-per-line) and inside the --json events array. Rows are hand-built so
the cursors are JSON numbers like the other three CLIs, not the proto3 JSON
mapping's quoted uint64 strings — the CrossLanguageSmokeMatrix divergence
table collapses to a single converged contract.
Tests: .NET MxGatewayClientCliTests 35/35 (new RendersReplayGapAsTypedRow
covers jsonl + aggregate); Java gradle test 52/52 (new
streamEventsRendersReplayGapAsTypedRow covers --json + text over the
in-process harness). No generated-file churn.
204 lines
9.3 KiB
Markdown
204 lines
9.3 KiB
Markdown
# Cross-Language Smoke Matrix
|
|
|
|
The cross-language smoke matrix defines the documented commands used to compare
|
|
official clients against the same live gateway flow. It is a repository
|
|
validation fixture and command reference; normal unit tests validate the matrix
|
|
shape without connecting to a gateway.
|
|
|
|
The matrix lives in
|
|
`clients/proto/fixtures/smoke/cross-language-smoke-matrix.json`.
|
|
|
|
## Scope
|
|
|
|
The matrix covers the supported client languages:
|
|
|
|
- .NET
|
|
- Go
|
|
- Rust
|
|
- Python
|
|
- Java
|
|
|
|
Each client entry defines commands for the same required operation sequence:
|
|
|
|
1. `open-session`
|
|
2. `register`
|
|
3. `add-item`
|
|
4. `advise`
|
|
5. `stream-events`
|
|
6. `close-session`
|
|
|
|
The optional `write` command is documented separately because writing changes
|
|
provider state and should only run when the operator supplies a safe test value.
|
|
|
|
When `stream-events` is resumed with an `after_worker_sequence` cursor that
|
|
predates the oldest event still in the gateway's replay ring, the gateway emits a
|
|
single `ReplayGap` sentinel at the head of the stream. Every client surfaces this
|
|
as a distinct, typed, non-terminal signal (see each client README); the resume
|
|
contract is `after_worker_sequence = oldest_available_sequence - 1`, and it holds
|
|
even when the ring has been emptied entirely by age eviction — the gateway then
|
|
reports the next deliverable sequence rather than `0` (see [Sessions](Sessions.md)).
|
|
The default smoke sequence opens a fresh stream (no cursor) and does not exercise
|
|
the gap path; a resume-with-gap fixture case is tracked separately (TST-24).
|
|
|
|
All five CLIs consume the typed gap and emit a dedicated row rather than a
|
|
degenerate event row (the .NET and Java halves were the last to convert — NEXT-02):
|
|
|
|
| CLI | Text mode | JSON mode |
|
|
|-----|-----------|-----------|
|
|
| `mxgw-rs` (Rust, canonical) | `REPLAY_GAP requested_after=<n> oldest_available=<n>` | `{"replayGap": {"requestedAfterSequence": <n>, "oldestAvailableSequence": <n>}}` as one entry of the `events` array |
|
|
| `mxgw-go` (Go) | `REPLAY_GAP requested_after=<n> oldest_available=<n>` | one `{"replayGap": {"requestedAfterSequence": <n>, "oldestAvailableSequence": <n>}}` line, counted toward `-limit` like any other row |
|
|
| `mxgw-py` (Python) | same JSON dump as `--json` | `{"replayGap": {"requestedAfterSequence": <n>, "oldestAvailableSequence": <n>}}` as one entry of the `events` array |
|
|
| `mxgw-dotnet` (.NET) | one `{"replayGap": {...}}` line (its "text" mode is JSON-per-line) | the same row — per line with `--jsonl`, as one entry of the `events` array with `--json` |
|
|
| `mxgw-java` (Java) | `REPLAY_GAP requested_after=<n> oldest_available=<n>` | one `{"replayGap": {"requestedAfterSequence": <n>, "oldestAvailableSequence": <n>}}` line |
|
|
|
|
All five emit the same two key names and, deliberately, the same JSON value
|
|
**types**: the cursors are JSON numbers (`7`), not strings. That is why every
|
|
CLI types the row by hand instead of marshalling `ReplayGap` through its
|
|
protobuf JSON formatter — the proto3 JSON mapping renders 64-bit integers as
|
|
strings (`"7"`). Normal event rows still come from the protobuf formatters, so
|
|
a matrix runner must still compare parsed values, not raw bytes, when it mixes
|
|
gap rows with event rows.
|
|
|
|
Two further formatting differences among the three canonical CLIs, none of them
|
|
semantic: Python sorts object keys and uses `", "` / `": "` separators
|
|
(`json.dumps(..., sort_keys=True)`), while Rust and Go emit compact,
|
|
declaration-ordered JSON; and the row sits alone on its own line for Go and for
|
|
Rust's `--jsonl`, but inside an `events` array for Python and for Rust's
|
|
aggregate `--json`.
|
|
|
|
## Integration Gate
|
|
|
|
Cross-language smoke execution is opt-in. Runners should skip the matrix unless
|
|
this variable is set:
|
|
|
|
```powershell
|
|
$env:MXGATEWAY_INTEGRATION = "1"
|
|
```
|
|
|
|
The shared inputs are:
|
|
|
|
| Variable | Default | Purpose |
|
|
|----------|---------|---------|
|
|
| `MXGATEWAY_ENDPOINT` | `localhost:5000` | Gateway endpoint used by client CLIs. |
|
|
| `MXGATEWAY_API_KEY` | Empty | API key source for authenticated gateway deployments. |
|
|
| `MXGATEWAY_TEST_ITEM` | `TestChildObject.TestInt` | MXAccess item used by `add-item`. |
|
|
| `MXGATEWAY_TEST_WRITE_VALUE` | Empty | Enables the optional write step when set by a runner. |
|
|
|
|
The commands in the matrix use `MXGATEWAY_API_KEY` through each CLI's
|
|
`api-key-env` flag. They must not embed bearer tokens or raw API keys.
|
|
|
|
### Credential contract for `authenticate-user`
|
|
|
|
Every CLI resolves the MXAccess verify-user credential the same way, so one
|
|
exported variable drives the same operator workflow in all five languages:
|
|
|
|
| Variable | Default | Purpose |
|
|
|----------|---------|---------|
|
|
| `MXGATEWAY_VERIFY_PASSWORD` | Empty | Verify-user credential read by `authenticate-user` when `--password` is omitted. |
|
|
|
|
- Flags are `--password` (Go: `-password`) for an explicit value and
|
|
`--password-env` (Go: `-password-env`) for the *name* of the environment
|
|
variable, defaulting to `MXGATEWAY_VERIFY_PASSWORD`.
|
|
- Resolution order is flag, then environment variable. Prefer the variable: the
|
|
flag puts the secret in shell history and the process table.
|
|
- A resolved credential that is **missing or empty** is a usage error. The CLI
|
|
fails fast before dialing rather than authenticating with an empty password,
|
|
and the error names only the flag and the variable — never the value. Nothing
|
|
echoes the credential to stdout, stderr, or logs.
|
|
|
|
This is CLI argument validation, not an MXAccess parity exception: the client
|
|
*libraries* still transmit whatever credential they are given. Only the operator
|
|
tools refuse to fabricate an empty one.
|
|
|
|
The .NET CLI accepted `--verify-user-password`, `--verify-user-password-env`, and
|
|
`MXGATEWAY_VERIFY_USER_PASSWORD` before this contract was unified. Those names
|
|
remain as deprecated aliases for one release; new scripts must use the canonical
|
|
names above. The full .NET resolution order is `--password`,
|
|
`--verify-user-password`, the variable named by `--password-env` (or the
|
|
deprecated `--verify-user-password-env`, default `MXGATEWAY_VERIFY_PASSWORD`),
|
|
then `MXGATEWAY_VERIFY_USER_PASSWORD`.
|
|
|
|
### TLS variant
|
|
|
|
The matrix runs over plaintext (`h2c`) by default. A TLS variant exists but stays
|
|
a manual/opt-in run, consistent with the gate above, because it needs the gateway
|
|
started with an HTTPS endpoint (an `https://` `MXGATEWAY_ENDPOINT`) and each CLI
|
|
switched to its TLS flag (`--tls` / `-tls` / `--plaintext=false` /
|
|
`plaintext=False`). The clients are lenient by default and accept the gateway's
|
|
auto-generated self-signed certificate without extra trust setup, except the Rust
|
|
CLI, which is pin-only and needs `--ca-file` or `--require-certificate-validation`
|
|
(and Python uses trust-on-first-use). See
|
|
[Gateway Configuration — Automatic self-signed certificate](./GatewayConfiguration.md#automatic-self-signed-certificate)
|
|
and each client README for the per-client TLS flags.
|
|
|
|
## JSON Comparison
|
|
|
|
Every command in the matrix requests JSON output. A runner can compare the
|
|
normalized smoke record across languages with these fields:
|
|
|
|
- language,
|
|
- operation,
|
|
- session id,
|
|
- server handle,
|
|
- item handle,
|
|
- event count,
|
|
- event family,
|
|
- worker sequence,
|
|
- protocol status,
|
|
- HRESULT,
|
|
- status arrays,
|
|
- close status.
|
|
|
|
Failure output must include the client language, endpoint, and redacted auth
|
|
context. Auth context identifies the source, such as `MXGATEWAY_API_KEY`, but
|
|
does not include the secret value.
|
|
|
|
## Bundled Smoke Commands
|
|
|
|
Each client also exposes a bundled `smoke` command. Those commands are useful
|
|
for quick local checks, but the full cross-language matrix uses explicit
|
|
operation commands because not every bundled smoke command streams events yet.
|
|
The explicit sequence remains the parity baseline for issue-level validation.
|
|
|
|
## Per-CLI Subcommand Coverage
|
|
|
|
The matrix sequence itself is available everywhere, but the later single-item
|
|
session commands were not added to every CLI at the same time. A runner that
|
|
reaches beyond the required sequence must branch on language, so the current
|
|
deltas are specified here rather than left to be discovered:
|
|
|
|
| Subcommand | .NET | Rust | Go | Python | Java |
|
|
|------------|------|------|----|--------|------|
|
|
| `unregister` | yes | yes | no | no | no |
|
|
| `add-buffered-item` | yes | no | no | no | no |
|
|
| `set-buffered-update-interval` | yes | no | no | no | no |
|
|
| `suspend` | yes | no | no | no | no |
|
|
| `activate` | yes | no | no | no | no |
|
|
| `write-secured` | yes | yes | yes | yes | yes |
|
|
| `write-secured2` | yes | no | no | no | no |
|
|
| `authenticate-user` | yes | yes | yes | yes | yes |
|
|
| `archestra-user-to-id` | yes | no | no | no | no |
|
|
|
|
Only .NET exposes all nine. Rust adds `unregister` and the credential pair; Go,
|
|
Python, and Java expose the credential pair only. Every gap is CLI surface only —
|
|
all five *libraries* implement all nine typed helpers, so a gap is a missing
|
|
operator command, never a missing capability. Levelling the CLIs is separate
|
|
feature work and is not tracked as a defect here.
|
|
|
|
## Validation
|
|
|
|
Run the matrix shape tests after changing the smoke matrix:
|
|
|
|
```bash
|
|
dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter FullyQualifiedName~CrossLanguageSmokeMatrixTests
|
|
```
|
|
|
|
Live execution remains a separate opt-in step because it depends on a running
|
|
gateway, the installed MXAccess worker path, and provider state.
|
|
|
|
## Related Documentation
|
|
|
|
- [Gateway Testing](./GatewayTesting.md)
|
|
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
|
|
- [Client Proto Generation](./ClientProtoGeneration.md)
|