Resolve Client.Java-006..012 code-review findings

Client.Java-006: close() on both clients only called shutdown(). It now
awaits termination up to the connect timeout and shutdownNow()s on timeout.

Client.Java-007: added MxGatewayLowFindingsTests covering the alarm surface,
async streaming, MxEventStream overflow, and TLS channel construction. A
latent bug surfaced: a missing CA file throws IllegalArgumentException, not
SSLException — the channel-builder catch was broadened accordingly.

Client.Java-008: async thenApply sites now route stray RuntimeExceptions
through MxGatewayErrors.fromGrpc via a normalising validator.

Client.Java-009: extracted ~80 duplicated lines (createChannel, withDeadline,
toCompletable, ...) into a shared MxGatewayChannels; both clients delegate.

Client.Java-010 (re-triaged): the README's metadata:read scope was correct;
the acknowledgeAlarm Javadoc's invoke:alarm-ack was wrong — corrected to the
admin scope.

Client.Java-011: documented the intentional fail-fast event-stream
backpressure in Javadoc and the README.

Client.Java-012: replaced CommonOptions.resolved()'s mutate-and-return-this
with side-effect-free resolvedApiKey()/resolvedTimeout() accessors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-18 22:42:51 -04:00
parent 555fe4c0ba
commit 6eb9ea9105
8 changed files with 846 additions and 217 deletions
@@ -661,33 +661,60 @@ public final class MxGatewayCli implements Callable<Integer> {
@Option(names = "--timeout", defaultValue = "30s", description = "Per-call timeout.")
String timeout;
private String resolvedApiKey = "";
private Duration resolvedTimeout = Duration.ofSeconds(30);
/**
* Returns this options object unchanged.
*
* <p>Retained as a no-op for call sites that read more naturally as
* {@code common.resolved()}. Resolution of the API key and timeout is
* computed lazily on demand by {@link #resolvedApiKey()} and
* {@link #resolvedTimeout()}, so {@link #toClientOptions()} and
* {@link #redactedJsonMap()} produce correct output regardless of
* whether this method was ever called.
*
* @return this options object
*/
CommonOptions resolved() {
resolvedApiKey = apiKey == null || apiKey.isBlank() ? System.getenv(apiKeyEnv) : apiKey;
if (resolvedApiKey == null) {
resolvedApiKey = "";
}
resolvedTimeout = parseDuration(timeout);
return this;
}
/**
* Resolves the effective API key: the explicit {@code --api-key} value
* when non-blank, otherwise the value of the {@code --api-key-env}
* environment variable, otherwise an empty string. Computed on each
* call so there is no stale cached state.
*
* @return the resolved API key, never {@code null}
*/
String resolvedApiKey() {
String resolved = apiKey == null || apiKey.isBlank() ? System.getenv(apiKeyEnv) : apiKey;
return resolved == null ? "" : resolved;
}
/**
* Resolves the effective per-call timeout from the {@code --timeout}
* option. Computed on each call so there is no stale cached state.
*
* @return the resolved call timeout
*/
Duration resolvedTimeout() {
return parseDuration(timeout);
}
MxGatewayClientOptions toClientOptions() {
return MxGatewayClientOptions.builder()
.endpoint(endpoint)
.apiKey(resolvedApiKey)
.apiKey(resolvedApiKey())
.plaintext(plaintext)
.caCertificatePath(caFile)
.serverNameOverride(serverNameOverride)
.callTimeout(resolvedTimeout)
.callTimeout(resolvedTimeout())
.build();
}
Map<String, Object> redactedJsonMap() {
Map<String, Object> values = new LinkedHashMap<>();
values.put("endpoint", endpoint);
values.put("apiKey", MxGatewaySecrets.redactApiKey(resolvedApiKey));
values.put("apiKey", MxGatewaySecrets.redactApiKey(resolvedApiKey()));
values.put("apiKeyEnv", apiKeyEnv);
values.put("plaintext", plaintext);
values.put("caFile", caFile == null ? "" : caFile.toString());