CLI-40: port the exact-secret credential scrub to Rust/Java/.NET (Go/Python
already did it). AuthenticateUser/WriteSecured(2) helpers now redact the exact
caller-supplied secret from any surfaced error, as defense-in-depth on top of the
by-construction guarantee. Rust hand-writes a redacting Debug (derived Debug would
leak the reply); Java/.NET rebuild the same exception type with the redacted
message and do not carry the secret-bearing original forward (so ToString/stack
traces stay clean too).
CLI-41: uniform malformed-reply contract for AuthenticateUser/ArchestrAUserToId/
AddBufferedItem across all five clients — typed payload, else a present int32
return_value, else a typed malformed-reply error. Fixes Go/Java silent-0, .NET
NRE, and Rust's own internal inconsistency.
CLI-44: the Go event goroutine's Recv-error path now uses a non-blocking
sendTerminalEventResult on the reserved slot, so a genuine terminal stream error
is reported as itself instead of being mislabeled ErrSlowConsumer under overflow.
Riders from the CLI-37/38 review: (a) .NET ToDiagnosticSummary and Python
_mxaccess_message surface the raw success member (diagnostics-only parity with
Rust); (b) the status-conversion fixture carries an independent wantSuccess
boolean and the Go/.NET fixture tests assert against it instead of recomputing
the formula under test.
Shared fixtures (authenticate-user.{echoed-credential,missing-payload,
return-value-only}.reply.json) + manifest + ClientBehaviorFixtures.md +
ClientLibrariesDesign.md updated in the same change. Tracking: CLI-40/41/44 -> Done.
7.7 KiB
Client Behavior Fixtures
Client behavior fixtures define the shared expectations used by the official .NET, Go, Rust, Python, and Java clients. They keep wrapper behavior aligned while each language exposes idiomatic APIs over the same protobuf contract.
Fixture Set
The fixture manifest is clients/proto/fixtures/behavior/manifest.json.
clients/proto/proto-inputs.json references the fixture root through
behaviorFixtureRoot so generators and client test projects can discover the
same files they use for descriptor inputs.
The fixture set contains:
- command reply protobuf JSON,
- ordered event stream protobuf JSON samples,
MxValueconversion case sets,MxStatusProxyconversion case sets,- authentication and authorization error expectations,
- timeout and cancellation behavior expectations.
Protobuf message fixtures use protobuf JSON field names and enum values. Files that describe client wrapper behavior use explicit JSON fields instead of a proto message because those expectations apply above the generated transport types.
Command Replies
Command reply fixtures live in
clients/proto/fixtures/behavior/command-replies/. They parse as
mxaccess_gateway.v1.MxCommandReply.
Clients use these fixtures to verify that successful and failed MXAccess commands both carry the full reply details:
protocolStatus,hresult,returnValue,- repeated
statuses, - method-specific reply payloads when MXAccess returns out parameters.
MXAccess failures remain command replies when the gateway reached the worker and
the worker captured HRESULT or MXSTATUS_PROXY details. Client wrappers should
map those replies to rich command errors without discarding the raw reply.
Reply Validation Conformance
Four command reply fixtures pin the two reply-validation rules every client applies, because both rules have edges where a naive reading disagrees with the wire contract:
| Fixture | Reply | Expected verdict |
|---|---|---|
write.status-category-error-success-set.reply.json |
one status with success = 1, category = MX_STATUS_CATEGORY_COMMUNICATION_ERROR |
failure |
write.status-category-ok-success-zero.reply.json |
one status with success = 0, category = MX_STATUS_CATEGORY_OK |
success |
write.hresult-s-false.reply.json |
hresult = 1 (S_FALSE), statuses OK |
success |
write.hresult-e-fail.reply.json |
hresult = -2147467259 (E_FAIL), statuses OK |
failure |
The rules those fixtures lock in are:
- Status entries. An
MxStatusProxyentry is a failure exactly whencategory != MX_STATUS_CATEGORY_OK.successmirrors the raw 16-bit COM member and is diagnostics only, so it never participates in the verdict — the proto contract makescategoryauthoritative. An absent entry is success (nothing was reported); a present entry withMX_STATUS_CATEGORY_UNSPECIFIEDis a failure, because the worker always maps a category and an unmapped one is not proven OK. - HRESULT. A reply fails on HRESULT exactly when
hresultis present and negative. Positive COM success codes such asS_FALSEpass, matching COM semantics.
Malformed-Reply And Credential-Redaction Conformance
Three further command reply fixtures pin the id/handle-extraction and credential-redaction contracts for the credential-bearing helpers:
| Fixture | Reply | Expected behavior |
|---|---|---|
authenticate-user.echoed-credential.reply.json |
OK envelope, negative hresult, and the caller's credential echoed into protocolStatus.message, statuses[0].diagnosticText, and diagnosticMessage |
the surfaced error redacts the exact secret (never leaks the verbatim value) |
authenticate-user.missing-payload.reply.json |
OK envelope, no AuthenticateUser payload, no return_value |
a typed malformed-reply error, never a proto3 default 0 and never an NRE |
authenticate-user.return-value-only.reply.json |
OK envelope, return_value.int32_value = 7, no typed payload |
the id resolves to 7 via the legacy return_value compatibility path |
The rules those fixtures lock in are:
- Malformed-reply extraction (CLI-41). Every helper that extracts a scalar
id/handle (
AuthenticateUser,ArchestrAUserToId,AddBufferedItem, and the handle extractors) prefers the typed payload; when it is absent it falls back toreturn_valueonly whenreturn_valueis present with the expected int32 variant; when neither is present it raises a typed malformed-reply error. It never surfaces a proto3 default0and never throws a null-reference. - Credential redaction (CLI-40). The credential-bearing helpers
(
AuthenticateUser,WriteSecured/WriteSecured2) scrub the exact secret values they were called with from any surfaced error text, replacing each occurrence with the client's redaction marker. This is defense-in-depth on top of the by-construction guarantee that exceptions carry reply-derived text, not the request. The marker is<redacted>in the Go, Rust, and Java clients and[redacted]in the Python client and the .NET CLI; the assertion each suite makes is that the surfaced message no longer contains the credential and does contain the client's marker.
Event Streams
Event stream fixtures live in
clients/proto/fixtures/behavior/event-streams/. Each file contains an ordered
events array whose entries parse as mxaccess_gateway.v1.MxEvent.
Clients use these fixtures to verify that stream helpers preserve
workerSequence order and expose each native event family:
OnDataChange,OnWriteComplete,OperationComplete,OnBufferedDataChange.
Wrappers must not reorder, coalesce, or drop events while reading the fixture.
Value And Status Conversion
Value fixtures live in clients/proto/fixtures/behavior/values/. Each case
contains a value object that parses as mxaccess_gateway.v1.MxValue.
Status fixtures live in clients/proto/fixtures/behavior/statuses/. Each case
contains a status object that parses as
mxaccess_gateway.v1.MxStatusProxy.
Clients use these fixtures to verify typed projections and raw fallback
behavior. A language helper may expose native booleans, integers, strings,
arrays, and timestamps, but it must keep rawDiagnostic, raw data type fields,
and raw byte payloads accessible when conversion is incomplete.
Each status case also carries an independent wantSuccess boolean alongside its
status object. The success/failure conformance tests assert the helper's
verdict against this fixture-declared expectation rather than recomputing it from
category (the same formula under test), so a regression in the verdict rule
cannot hide behind a self-consistent computation.
Auth, Timeout, And Cancel Behavior
Authentication fixtures live in clients/proto/fixtures/behavior/auth/. They
separate UNAUTHENTICATED from PERMISSION_DENIED so clients map missing or
invalid credentials differently from missing scopes. Expected output strings
contain only redacted credentials.
Timeout and cancellation fixtures live in
clients/proto/fixtures/behavior/timeout-cancel/. They document that canceling
or timing out a client call stops the client from waiting, but it does not abort
an in-flight MXAccess COM call on the worker STA. Clients should follow up with
GetSessionState or CloseSession before reusing handles after an uncertain
command timeout.
Validation
Run the fixture validation tests after changing the behavior fixture set:
powershell -ExecutionPolicy Bypass -File scripts/validate-client-behavior-fixtures.ps1
The script runs the focused C# contract tests that parse all protobuf JSON fixtures and validate deterministic wrapper expectation files.