Compare commits

..

4 Commits

Author SHA1 Message Date
Joseph Doherty 0a670eb381 Issue #35: add parity fixture matrix 2026-04-26 20:47:05 -04:00
dohertj2 14afb325c3 Merge pull request #96 from agent-1/issue-47-scaffold-java-gradle-build
Issue #47: scaffold Java Gradle build
2026-04-26 20:42:39 -04:00
Joseph Doherty af42891d5a Issue #47: scaffold Java Gradle build 2026-04-26 20:36:27 -04:00
dohertj2 01a51df053 Merge pull request #95 from agent-2/issue-44-implement-rust-client-session-values-errors-and-cli
Issue #44: implement Rust client session values errors and CLI
2026-04-26 20:34:28 -04:00
18 changed files with 68945 additions and 3 deletions
+41
View File
@@ -0,0 +1,41 @@
# Java Client
The Java client workspace contains the Gradle scaffold for the MXAccess Gateway
client library, generated protobuf/gRPC bindings, a test CLI project, and JUnit
tests.
## Layout
```text
clients/java/
settings.gradle
build.gradle
src/main/generated/
mxgateway-client/
mxgateway-cli/
```
`mxgateway-client` generates Java protobuf and gRPC sources from
`../../src/MxGateway.Contracts/Protos`. The Gradle protobuf plugin writes those
generated sources under `src/main/generated`, which matches the client proto
manifest in `../proto/proto-inputs.json`. Do not edit generated files by hand.
`mxgateway-cli` depends on `mxgateway-client` and provides the `mxgw-java`
application entry point used by later CLI implementation work.
## Build And Test
Run the Java checks from `clients/java`:
```powershell
gradle test
```
The build uses the Java 21 Gradle toolchain, compiles generated protobuf/gRPC
code, and runs JUnit 5 tests for the scaffold and CLI entry point.
## Related Documentation
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Java Client Detailed Design](../../docs/clients-java-design.md)
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
+38
View File
@@ -0,0 +1,38 @@
plugins {
id 'base'
}
ext {
grpcVersion = '1.76.0'
junitVersion = '5.14.1'
picocliVersion = '4.7.7'
protobufVersion = '4.33.1'
}
subprojects {
group = 'com.dohertylan.mxgateway'
version = '0.1.0'
pluginManager.withPlugin('java') {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.release = 21
}
tasks.withType(Test).configureEach {
useJUnitPlatform()
}
dependencies {
testImplementation platform("org.junit:junit-bom:${junitVersion}")
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
}
}
+12
View File
@@ -0,0 +1,12 @@
plugins {
id 'application'
}
dependencies {
implementation project(':mxgateway-client')
implementation "info.picocli:picocli:${picocliVersion}"
}
application {
mainClass = 'com.dohertylan.mxgateway.cli.MxGatewayCli'
}
@@ -0,0 +1,53 @@
package com.dohertylan.mxgateway.cli;
import com.dohertylan.mxgateway.client.MxGatewayClientVersion;
import java.io.PrintWriter;
import java.util.concurrent.Callable;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Spec;
@Command(
name = "mxgw-java",
mixinStandardHelpOptions = true,
description = "MXAccess Gateway Java test CLI.",
subcommands = MxGatewayCli.VersionCommand.class)
public final class MxGatewayCli implements Callable<Integer> {
@Spec
private CommandSpec spec;
public static void main(String[] args) {
int exitCode = new CommandLine(new MxGatewayCli()).execute(args);
System.exit(exitCode);
}
public static int execute(PrintWriter out, PrintWriter err, String... args) {
CommandLine commandLine = new CommandLine(new MxGatewayCli());
commandLine.setOut(out);
commandLine.setErr(err);
return commandLine.execute(args);
}
@Override
public Integer call() {
spec.commandLine().usage(spec.commandLine().getOut());
return 0;
}
@Command(name = "version", description = "Prints the Java client scaffold version.")
public static final class VersionCommand implements Callable<Integer> {
@Spec
private CommandSpec spec;
@Override
public Integer call() {
spec.commandLine().getOut().printf(
"mxgateway-java %s gatewayProtocolVersion=%d workerProtocolVersion=%d%n",
MxGatewayClientVersion.clientVersion(),
MxGatewayClientVersion.gatewayProtocolVersion(),
MxGatewayClientVersion.workerProtocolVersion());
return 0;
}
}
}
@@ -0,0 +1,27 @@
package com.dohertylan.mxgateway.cli;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.junit.jupiter.api.Test;
final class MxGatewayCliTests {
@Test
void versionCommandPrintsProtocolVersions() {
StringWriter output = new StringWriter();
StringWriter errors = new StringWriter();
int exitCode = MxGatewayCli.execute(
new PrintWriter(output, true),
new PrintWriter(errors, true),
"version");
assertEquals(0, exitCode);
assertEquals("", errors.toString());
assertTrue(output.toString().contains("mxgateway-java 0.1.0"));
assertTrue(output.toString().contains("gatewayProtocolVersion=1"));
assertTrue(output.toString().contains("workerProtocolVersion=1"));
}
}
@@ -0,0 +1,46 @@
plugins {
id 'java-library'
id 'com.google.protobuf'
}
dependencies {
api "com.google.protobuf:protobuf-java:${protobufVersion}"
api "io.grpc:grpc-protobuf:${grpcVersion}"
api "io.grpc:grpc-stub:${grpcVersion}"
implementation "io.grpc:grpc-netty-shaded:${grpcVersion}"
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
}
sourceSets {
main {
proto {
srcDir rootProject.file('../../src/MxGateway.Contracts/Protos')
include 'mxaccess_gateway.proto'
include 'mxaccess_worker.proto'
}
}
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${protobufVersion}"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
}
}
generatedFilesBaseDir = rootProject.file('src/main/generated').absolutePath
generateProtoTasks {
all().configureEach {
plugins {
grpc {}
}
}
}
}
@@ -0,0 +1,22 @@
package com.dohertylan.mxgateway.client;
public final class MxGatewayClientVersion {
private static final int GATEWAY_PROTOCOL_VERSION = 1;
private static final int WORKER_PROTOCOL_VERSION = 1;
private static final String CLIENT_VERSION = "0.1.0";
private MxGatewayClientVersion() {
}
public static String clientVersion() {
return CLIENT_VERSION;
}
public static int gatewayProtocolVersion() {
return GATEWAY_PROTOCOL_VERSION;
}
public static int workerProtocolVersion() {
return WORKER_PROTOCOL_VERSION;
}
}
@@ -0,0 +1,29 @@
package com.dohertylan.mxgateway.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import mxaccess_gateway.v1.MxAccessGatewayGrpc;
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest;
import mxaccess_worker.v1.MxaccessWorker.WorkerEnvelope;
import org.junit.jupiter.api.Test;
final class GeneratedContractSmokeTests {
@Test
void generatedGatewayAndWorkerContractsCompile() {
OpenSessionRequest request = OpenSessionRequest.newBuilder()
.setClientSessionName("junit")
.build();
WorkerEnvelope envelope = WorkerEnvelope.newBuilder()
.setProtocolVersion(MxGatewayClientVersion.workerProtocolVersion())
.build();
assertEquals("junit", request.getClientSessionName());
assertEquals("mxaccess_gateway.v1.MxAccessGateway", MxAccessGatewayGrpc.SERVICE_NAME);
assertEquals(1, envelope.getProtocolVersion());
}
@Test
void javaTwentyOneToolchainRunsTests() {
assertEquals(21, Runtime.version().feature());
}
}
+22
View File
@@ -0,0 +1,22 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
plugins {
id 'com.google.protobuf' version '0.9.5'
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
}
rootProject.name = 'mxaccessgw-java'
include 'mxgateway-client'
include 'mxgateway-cli'
@@ -0,0 +1,588 @@
package mxaccess_gateway.v1;
import static io.grpc.MethodDescriptor.generateFullMethodName;
/**
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
@io.grpc.stub.annotations.GrpcGenerated
public final class MxAccessGatewayGrpc {
private MxAccessGatewayGrpc() {}
public static final java.lang.String SERVICE_NAME = "mxaccess_gateway.v1.MxAccessGateway";
// Static method descriptors that strictly reflect the proto.
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest,
mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply> getOpenSessionMethod;
@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "OpenSession",
requestType = mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply.class,
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest,
mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply> getOpenSessionMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest, mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply> getOpenSessionMethod;
if ((getOpenSessionMethod = MxAccessGatewayGrpc.getOpenSessionMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) {
if ((getOpenSessionMethod = MxAccessGatewayGrpc.getOpenSessionMethod) == null) {
MxAccessGatewayGrpc.getOpenSessionMethod = getOpenSessionMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest, mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "OpenSession"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("OpenSession"))
.build();
}
}
}
return getOpenSessionMethod;
}
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest,
mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply> getCloseSessionMethod;
@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "CloseSession",
requestType = mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply.class,
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest,
mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply> getCloseSessionMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest, mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply> getCloseSessionMethod;
if ((getCloseSessionMethod = MxAccessGatewayGrpc.getCloseSessionMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) {
if ((getCloseSessionMethod = MxAccessGatewayGrpc.getCloseSessionMethod) == null) {
MxAccessGatewayGrpc.getCloseSessionMethod = getCloseSessionMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest, mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "CloseSession"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("CloseSession"))
.build();
}
}
}
return getCloseSessionMethod;
}
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest,
mxaccess_gateway.v1.MxaccessGateway.MxCommandReply> getInvokeMethod;
@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "Invoke",
requestType = mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.MxCommandReply.class,
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest,
mxaccess_gateway.v1.MxaccessGateway.MxCommandReply> getInvokeMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest, mxaccess_gateway.v1.MxaccessGateway.MxCommandReply> getInvokeMethod;
if ((getInvokeMethod = MxAccessGatewayGrpc.getInvokeMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) {
if ((getInvokeMethod = MxAccessGatewayGrpc.getInvokeMethod) == null) {
MxAccessGatewayGrpc.getInvokeMethod = getInvokeMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest, mxaccess_gateway.v1.MxaccessGateway.MxCommandReply>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "Invoke"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.MxCommandReply.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("Invoke"))
.build();
}
}
}
return getInvokeMethod;
}
private static volatile io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest,
mxaccess_gateway.v1.MxaccessGateway.MxEvent> getStreamEventsMethod;
@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "StreamEvents",
requestType = mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest.class,
responseType = mxaccess_gateway.v1.MxaccessGateway.MxEvent.class,
methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
public static io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest,
mxaccess_gateway.v1.MxaccessGateway.MxEvent> getStreamEventsMethod() {
io.grpc.MethodDescriptor<mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest, mxaccess_gateway.v1.MxaccessGateway.MxEvent> getStreamEventsMethod;
if ((getStreamEventsMethod = MxAccessGatewayGrpc.getStreamEventsMethod) == null) {
synchronized (MxAccessGatewayGrpc.class) {
if ((getStreamEventsMethod = MxAccessGatewayGrpc.getStreamEventsMethod) == null) {
MxAccessGatewayGrpc.getStreamEventsMethod = getStreamEventsMethod =
io.grpc.MethodDescriptor.<mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest, mxaccess_gateway.v1.MxaccessGateway.MxEvent>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "StreamEvents"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
mxaccess_gateway.v1.MxaccessGateway.MxEvent.getDefaultInstance()))
.setSchemaDescriptor(new MxAccessGatewayMethodDescriptorSupplier("StreamEvents"))
.build();
}
}
}
return getStreamEventsMethod;
}
/**
* Creates a new async stub that supports all call types for the service
*/
public static MxAccessGatewayStub newStub(io.grpc.Channel channel) {
io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayStub> factory =
new io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayStub>() {
@java.lang.Override
public MxAccessGatewayStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayStub(channel, callOptions);
}
};
return MxAccessGatewayStub.newStub(factory, channel);
}
/**
* Creates a new blocking-style stub that supports all types of calls on the service
*/
public static MxAccessGatewayBlockingV2Stub newBlockingV2Stub(
io.grpc.Channel channel) {
io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayBlockingV2Stub> factory =
new io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayBlockingV2Stub>() {
@java.lang.Override
public MxAccessGatewayBlockingV2Stub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayBlockingV2Stub(channel, callOptions);
}
};
return MxAccessGatewayBlockingV2Stub.newStub(factory, channel);
}
/**
* Creates a new blocking-style stub that supports unary and streaming output calls on the service
*/
public static MxAccessGatewayBlockingStub newBlockingStub(
io.grpc.Channel channel) {
io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayBlockingStub> factory =
new io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayBlockingStub>() {
@java.lang.Override
public MxAccessGatewayBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayBlockingStub(channel, callOptions);
}
};
return MxAccessGatewayBlockingStub.newStub(factory, channel);
}
/**
* Creates a new ListenableFuture-style stub that supports unary calls on the service
*/
public static MxAccessGatewayFutureStub newFutureStub(
io.grpc.Channel channel) {
io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayFutureStub> factory =
new io.grpc.stub.AbstractStub.StubFactory<MxAccessGatewayFutureStub>() {
@java.lang.Override
public MxAccessGatewayFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayFutureStub(channel, callOptions);
}
};
return MxAccessGatewayFutureStub.newStub(factory, channel);
}
/**
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
public interface AsyncService {
/**
*/
default void openSession(mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getOpenSessionMethod(), responseObserver);
}
/**
*/
default void closeSession(mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getCloseSessionMethod(), responseObserver);
}
/**
*/
default void invoke(mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxCommandReply> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getInvokeMethod(), responseObserver);
}
/**
*/
default void streamEvents(mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getStreamEventsMethod(), responseObserver);
}
}
/**
* Base class for the server implementation of the service MxAccessGateway.
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
public static abstract class MxAccessGatewayImplBase
implements io.grpc.BindableService, AsyncService {
@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
return MxAccessGatewayGrpc.bindService(this);
}
}
/**
* A stub to allow clients to do asynchronous rpc calls to service MxAccessGateway.
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
public static final class MxAccessGatewayStub
extends io.grpc.stub.AbstractAsyncStub<MxAccessGatewayStub> {
private MxAccessGatewayStub(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
super(channel, callOptions);
}
@java.lang.Override
protected MxAccessGatewayStub build(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayStub(channel, callOptions);
}
/**
*/
public void openSession(mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getOpenSessionMethod(), getCallOptions()), request, responseObserver);
}
/**
*/
public void closeSession(mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getCloseSessionMethod(), getCallOptions()), request, responseObserver);
}
/**
*/
public void invoke(mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxCommandReply> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getInvokeMethod(), getCallOptions()), request, responseObserver);
}
/**
*/
public void streamEvents(mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest request,
io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent> responseObserver) {
io.grpc.stub.ClientCalls.asyncServerStreamingCall(
getChannel().newCall(getStreamEventsMethod(), getCallOptions()), request, responseObserver);
}
}
/**
* A stub to allow clients to do synchronous rpc calls to service MxAccessGateway.
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
public static final class MxAccessGatewayBlockingV2Stub
extends io.grpc.stub.AbstractBlockingStub<MxAccessGatewayBlockingV2Stub> {
private MxAccessGatewayBlockingV2Stub(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
super(channel, callOptions);
}
@java.lang.Override
protected MxAccessGatewayBlockingV2Stub build(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayBlockingV2Stub(channel, callOptions);
}
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply openSession(mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
getChannel(), getOpenSessionMethod(), getCallOptions(), request);
}
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply closeSession(mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
getChannel(), getCloseSessionMethod(), getCallOptions(), request);
}
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.MxCommandReply invoke(mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest request) throws io.grpc.StatusException {
return io.grpc.stub.ClientCalls.blockingV2UnaryCall(
getChannel(), getInvokeMethod(), getCallOptions(), request);
}
/**
*/
@io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918")
public io.grpc.stub.BlockingClientCall<?, mxaccess_gateway.v1.MxaccessGateway.MxEvent>
streamEvents(mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest request) {
return io.grpc.stub.ClientCalls.blockingV2ServerStreamingCall(
getChannel(), getStreamEventsMethod(), getCallOptions(), request);
}
}
/**
* A stub to allow clients to do limited synchronous rpc calls to service MxAccessGateway.
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
public static final class MxAccessGatewayBlockingStub
extends io.grpc.stub.AbstractBlockingStub<MxAccessGatewayBlockingStub> {
private MxAccessGatewayBlockingStub(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
super(channel, callOptions);
}
@java.lang.Override
protected MxAccessGatewayBlockingStub build(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayBlockingStub(channel, callOptions);
}
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply openSession(mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getOpenSessionMethod(), getCallOptions(), request);
}
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply closeSession(mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getCloseSessionMethod(), getCallOptions(), request);
}
/**
*/
public mxaccess_gateway.v1.MxaccessGateway.MxCommandReply invoke(mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getInvokeMethod(), getCallOptions(), request);
}
/**
*/
public java.util.Iterator<mxaccess_gateway.v1.MxaccessGateway.MxEvent> streamEvents(
mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest request) {
return io.grpc.stub.ClientCalls.blockingServerStreamingCall(
getChannel(), getStreamEventsMethod(), getCallOptions(), request);
}
}
/**
* A stub to allow clients to do ListenableFuture-style rpc calls to service MxAccessGateway.
* <pre>
* Public client API for MXAccess sessions hosted by the gateway.
* </pre>
*/
public static final class MxAccessGatewayFutureStub
extends io.grpc.stub.AbstractFutureStub<MxAccessGatewayFutureStub> {
private MxAccessGatewayFutureStub(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
super(channel, callOptions);
}
@java.lang.Override
protected MxAccessGatewayFutureStub build(
io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
return new MxAccessGatewayFutureStub(channel, callOptions);
}
/**
*/
public com.google.common.util.concurrent.ListenableFuture<mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply> openSession(
mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest request) {
return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getOpenSessionMethod(), getCallOptions()), request);
}
/**
*/
public com.google.common.util.concurrent.ListenableFuture<mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply> closeSession(
mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest request) {
return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getCloseSessionMethod(), getCallOptions()), request);
}
/**
*/
public com.google.common.util.concurrent.ListenableFuture<mxaccess_gateway.v1.MxaccessGateway.MxCommandReply> invoke(
mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest request) {
return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getInvokeMethod(), getCallOptions()), request);
}
}
private static final int METHODID_OPEN_SESSION = 0;
private static final int METHODID_CLOSE_SESSION = 1;
private static final int METHODID_INVOKE = 2;
private static final int METHODID_STREAM_EVENTS = 3;
private static final class MethodHandlers<Req, Resp> implements
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
private final AsyncService serviceImpl;
private final int methodId;
MethodHandlers(AsyncService serviceImpl, int methodId) {
this.serviceImpl = serviceImpl;
this.methodId = methodId;
}
@java.lang.Override
@java.lang.SuppressWarnings("unchecked")
public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
switch (methodId) {
case METHODID_OPEN_SESSION:
serviceImpl.openSession((mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply>) responseObserver);
break;
case METHODID_CLOSE_SESSION:
serviceImpl.closeSession((mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply>) responseObserver);
break;
case METHODID_INVOKE:
serviceImpl.invoke((mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxCommandReply>) responseObserver);
break;
case METHODID_STREAM_EVENTS:
serviceImpl.streamEvents((mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest) request,
(io.grpc.stub.StreamObserver<mxaccess_gateway.v1.MxaccessGateway.MxEvent>) responseObserver);
break;
default:
throw new AssertionError();
}
}
@java.lang.Override
@java.lang.SuppressWarnings("unchecked")
public io.grpc.stub.StreamObserver<Req> invoke(
io.grpc.stub.StreamObserver<Resp> responseObserver) {
switch (methodId) {
default:
throw new AssertionError();
}
}
}
public static final io.grpc.ServerServiceDefinition bindService(AsyncService service) {
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
.addMethod(
getOpenSessionMethod(),
io.grpc.stub.ServerCalls.asyncUnaryCall(
new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest,
mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply>(
service, METHODID_OPEN_SESSION)))
.addMethod(
getCloseSessionMethod(),
io.grpc.stub.ServerCalls.asyncUnaryCall(
new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest,
mxaccess_gateway.v1.MxaccessGateway.CloseSessionReply>(
service, METHODID_CLOSE_SESSION)))
.addMethod(
getInvokeMethod(),
io.grpc.stub.ServerCalls.asyncUnaryCall(
new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest,
mxaccess_gateway.v1.MxaccessGateway.MxCommandReply>(
service, METHODID_INVOKE)))
.addMethod(
getStreamEventsMethod(),
io.grpc.stub.ServerCalls.asyncServerStreamingCall(
new MethodHandlers<
mxaccess_gateway.v1.MxaccessGateway.StreamEventsRequest,
mxaccess_gateway.v1.MxaccessGateway.MxEvent>(
service, METHODID_STREAM_EVENTS)))
.build();
}
private static abstract class MxAccessGatewayBaseDescriptorSupplier
implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
MxAccessGatewayBaseDescriptorSupplier() {}
@java.lang.Override
public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
return mxaccess_gateway.v1.MxaccessGateway.getDescriptor();
}
@java.lang.Override
public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
return getFileDescriptor().findServiceByName("MxAccessGateway");
}
}
private static final class MxAccessGatewayFileDescriptorSupplier
extends MxAccessGatewayBaseDescriptorSupplier {
MxAccessGatewayFileDescriptorSupplier() {}
}
private static final class MxAccessGatewayMethodDescriptorSupplier
extends MxAccessGatewayBaseDescriptorSupplier
implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
private final java.lang.String methodName;
MxAccessGatewayMethodDescriptorSupplier(java.lang.String methodName) {
this.methodName = methodName;
}
@java.lang.Override
public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
return getServiceDescriptor().findMethodByName(methodName);
}
}
private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
public static io.grpc.ServiceDescriptor getServiceDescriptor() {
io.grpc.ServiceDescriptor result = serviceDescriptor;
if (result == null) {
synchronized (MxAccessGatewayGrpc.class) {
result = serviceDescriptor;
if (result == null) {
serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
.setSchemaDescriptor(new MxAccessGatewayFileDescriptorSupplier())
.addMethod(getOpenSessionMethod())
.addMethod(getCloseSessionMethod())
.addMethod(getInvokeMethod())
.addMethod(getStreamEventsMethod())
.build();
}
}
}
return result;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,469 @@
{
"schemaVersion": 1,
"fixtureSet": "mxaccess-gateway-parity-fixture-matrix",
"contractName": "mxaccess-gateway",
"gatewayProtocolVersion": 1,
"workerProtocolVersion": 1,
"sourceCaptureRoot": "C:/Users/dohertj2/Desktop/mxaccess/captures",
"sourceDocs": [
"C:/Users/dohertj2/Desktop/mxaccess/docs/MXAccess-Public-API.md",
"C:/Users/dohertj2/Desktop/mxaccess/docs/Current-Sprint-State.md"
],
"comparisonFormat": {
"description": "Each parity run records the same command against direct MXAccess and the gateway-backed worker, then compares raw parity fields instead of client wrapper behavior.",
"directMxAccess": {
"requiredFields": [
"method",
"arguments",
"returnedValue",
"hresult",
"exceptionType",
"statuses",
"events"
]
},
"gatewayResult": {
"requiredFields": [
"kind",
"protocolStatus",
"returnValue",
"hresult",
"statuses",
"diagnosticMessage",
"events"
]
},
"eventFields": [
"family",
"serverHandle",
"itemHandle",
"value",
"quality",
"sourceTimestamp",
"statuses",
"workerSequence",
"workerTimestamp",
"gatewayReceiveTimestamp",
"hresult",
"rawStatus"
],
"comparisonKeys": [
"hresult",
"exceptionType",
"returnedValue",
"statusArrayShape",
"statusRawFields",
"eventFamilyOrder",
"eventPayloadShape",
"valueProjection",
"rawFallbackMetadata"
]
},
"methodFixtures": [
{
"id": "method.register.basic",
"method": "Register",
"commandKind": "MX_COMMAND_KIND_REGISTER",
"status": "planned_fixture",
"captureReferences": [
"captures/001-register/harness.log",
"captures/047-frida-com-proxy-register/harness.log"
],
"assertions": [
"preserve returned server handle in returnValue and RegisterReply",
"preserve success HRESULT as 0",
"do not emit MXAccess events for register"
]
},
{
"id": "method.unregister.basic",
"method": "Unregister",
"commandKind": "MX_COMMAND_KIND_UNREGISTER",
"status": "planned_fixture",
"captureReferences": [
"captures/001-register/harness.log",
"captures/109-native-post-remove-errors/harness.log"
],
"assertions": [
"preserve void return shape with explicit protocol success",
"preserve HRESULT or COM exception details for invalid server handle",
"close registered handle only after MXAccess succeeds"
]
},
{
"id": "method.add-item.scalar",
"method": "AddItem",
"commandKind": "MX_COMMAND_KIND_ADD_ITEM",
"status": "planned_fixture",
"captureReferences": [
"captures/002-add-remove-scalar/harness.log",
"captures/006-add-invalid/harness.log"
],
"assertions": [
"preserve returned item handle in returnValue and AddItemReply",
"preserve invalid item reference HRESULT/status details",
"do not prevalidate item definition in the gateway"
]
},
{
"id": "method.add-item2.context",
"method": "AddItem2",
"commandKind": "MX_COMMAND_KIND_ADD_ITEM2",
"status": "planned_fixture",
"captureReferences": [
"captures/mxaccess-additem2-testint-context.log",
"captures/121-frida-buffered-history-testhistoryvalue-context/harness.log"
],
"assertions": [
"pass item_definition and item_context exactly as supplied",
"preserve returned item handle in returnValue and AddItem2Reply",
"compare context-bearing reference resolution against direct MXAccess"
]
},
{
"id": "method.remove-item.basic",
"method": "RemoveItem",
"commandKind": "MX_COMMAND_KIND_REMOVE_ITEM",
"status": "planned_fixture",
"captureReferences": [
"captures/002-add-remove-scalar/harness.log",
"captures/109-native-post-remove-errors/harness.log"
],
"assertions": [
"preserve void return shape with explicit protocol success",
"preserve post-remove and invalid-handle HRESULT/status behavior",
"remove diagnostic handle state only after MXAccess succeeds"
]
},
{
"id": "method.advise.supervisory-data-change",
"method": "Advise",
"commandKind": "MX_COMMAND_KIND_ADVISE",
"status": "planned_fixture",
"captureReferences": [
"captures/003-subscribe-scalars/harness.log",
"captures/058-frida-subscribe-testint/harness.log"
],
"assertions": [
"preserve successful command reply shape",
"forward OnDataChange with value, quality, timestamp, and status array",
"preserve per-worker event order"
]
},
{
"id": "method.unadvise.basic",
"method": "UnAdvise",
"commandKind": "MX_COMMAND_KIND_UN_ADVISE",
"status": "planned_fixture",
"captureReferences": [
"captures/058-frida-subscribe-testint/harness.log",
"captures/007-subscribe-invalid/harness.log"
],
"assertions": [
"preserve void return shape with explicit protocol success",
"preserve invalid item handle HRESULT/status behavior",
"do not distinguish plain and supervisory cleanup beyond MXAccess behavior"
]
},
{
"id": "method.advise-supervisory.basic",
"method": "AdviseSupervisory",
"commandKind": "MX_COMMAND_KIND_ADVISE_SUPERVISORY",
"status": "planned_fixture",
"captureReferences": [
"captures/058-frida-subscribe-testint/harness.log",
"captures/105-frida-advise-shortdesc-prebound-fixed/harness.log"
],
"assertions": [
"keep AdviseSupervisory distinct from plain Advise in command kind",
"forward native OnDataChange only when MXAccess emits it",
"compare supervisory item status arrays without normalization"
]
},
{
"id": "method.add-buffered-item.context",
"method": "AddBufferedItem",
"commandKind": "MX_COMMAND_KIND_ADD_BUFFERED_ITEM",
"status": "planned_fixture",
"captureReferences": [
"captures/079-frida-add-buffered-advise-testint/harness.log",
"captures/120-frida-buffered-history-testhistoryvalue/harness.log",
"captures/121-frida-buffered-history-testhistoryvalue-context/harness.log"
],
"assertions": [
"pass item_definition and item_context exactly as supplied",
"preserve returned buffered item handle in returnValue and AddBufferedItemReply",
"keep buffered registration distinct from normal AddItem2"
]
},
{
"id": "method.set-buffered-update-interval.basic",
"method": "SetBufferedUpdateInterval",
"commandKind": "MX_COMMAND_KIND_SET_BUFFERED_UPDATE_INTERVAL",
"status": "planned_fixture",
"captureReferences": [
"captures/mxaccess-set-buffered-interval-1000.log",
"captures/079-frida-add-buffered-advise-testint/harness.log"
],
"assertions": [
"preserve requested update interval without clamping in the gateway",
"preserve void return shape with explicit protocol success",
"compare buffered event cadence only in opt-in live runs"
]
},
{
"id": "method.suspend.scan-state",
"method": "Suspend",
"commandKind": "MX_COMMAND_KIND_SUSPEND",
"status": "planned_fixture",
"captureReferences": [
"captures/077-frida-suspend-advised-scanstate/harness.log",
"captures/118-frida-suspend-advised-scanstate-long/harness.log"
],
"assertions": [
"preserve out MxStatus in SuspendReply and repeated statuses",
"preserve HRESULT separately from status detail",
"do not synthesize OperationComplete if native MXAccess does not raise it"
]
},
{
"id": "method.activate.scan-state",
"method": "Activate",
"commandKind": "MX_COMMAND_KIND_ACTIVATE",
"status": "planned_fixture",
"captureReferences": [
"captures/078-frida-activate-advised-scanstate/harness.log",
"captures/119-frida-activate-advised-scanstate-long/harness.log"
],
"assertions": [
"preserve out MxStatus in ActivateReply and repeated statuses",
"preserve HRESULT separately from status detail",
"do not synthesize OperationComplete if native MXAccess does not raise it"
]
},
{
"id": "method.write.value-status-matrix",
"method": "Write",
"commandKind": "MX_COMMAND_KIND_WRITE",
"status": "planned_fixture",
"captureReferences": [
"captures/023-frida-write-test-int-sequence-109-111/harness.log",
"captures/024-frida-write-test-bool-sequence/harness.log",
"captures/089-frida-write-testint-wrong-type/harness.log",
"captures/090-frida-write-invalid-reference/harness.log",
"captures/107-native-write-testint-current/harness.log"
],
"assertions": [
"preserve scalar and array value projections plus raw fallback metadata",
"preserve wrong-type and invalid-reference HRESULT/status arrays",
"forward OnWriteComplete only when native MXAccess emits it"
]
},
{
"id": "method.write2.timestamped",
"method": "Write2",
"commandKind": "MX_COMMAND_KIND_WRITE2",
"status": "planned_fixture",
"captureReferences": [
"captures/042-frida-write2-test-int-timestamp/harness.log",
"captures/066-frida-write2-test-bool-timestamp/harness.log",
"captures/075-frida-write2-test-datetime-array-timestamp/harness.log"
],
"assertions": [
"preserve timestamp_value as an MXAccess VARIANT projection",
"preserve write value shape and HRESULT/status arrays",
"compare timestamped write completion events against direct MXAccess"
]
},
{
"id": "method.write-secured.rejection-gap",
"method": "WriteSecured",
"commandKind": "MX_COMMAND_KIND_WRITE_SECURED",
"status": "documented_gap",
"captureReferences": [
"captures/036-frida-write-secured-test-int/harness.log",
"captures/111-frida-write-secured-auth-protectedvalue/harness.log",
"captures/112-frida-write-secured-auth-verified-protectedvalue1/harness.log"
],
"assertions": [
"preserve observed 0x80004021 rejection before a value-bearing NMX body",
"preserve current_user_id and verifier_user_id only as command inputs, not logs",
"upgrade this gap to planned_fixture when a successful direct WriteSecured path is observed"
]
},
{
"id": "method.write-secured2.authenticated",
"method": "WriteSecured2",
"commandKind": "MX_COMMAND_KIND_WRITE_SECURED2",
"status": "planned_fixture",
"captureReferences": [
"captures/113-frida-write-secured2-auth-protectedvalue/harness.log",
"captures/116-frida-write-secured2-auth-verified-protectedvalue1/harness.log",
"captures/117-frida-write-secured2-auth-testint/harness.log"
],
"assertions": [
"preserve authenticated timestamped secured write body shape",
"preserve HRESULT/status arrays without logging credential-bearing values",
"do not synthesize OnWriteComplete when direct MXAccess does not emit it"
]
},
{
"id": "method.authenticate-user.basic",
"method": "AuthenticateUser",
"commandKind": "MX_COMMAND_KIND_AUTHENTICATE_USER",
"status": "planned_fixture",
"captureReferences": [
"captures/087-frida-authenticate-administrator-empty/harness.log",
"captures/088-frida-authenticate-invalid-empty/harness.log"
],
"assertions": [
"preserve returned user id in returnValue and AuthenticateUserReply",
"preserve invalid credential HRESULT/status behavior",
"redact verify_user_password from logs and diagnostics"
]
},
{
"id": "method.archestra-user-to-id.basic",
"method": "ArchestrAUserToId",
"commandKind": "MX_COMMAND_KIND_ARCHESTRA_USER_TO_ID",
"status": "planned_fixture",
"captureReferences": [
"captures/mxaccess-user-map-administrator.log",
"captures/mxaccess-user-map-invalid.log"
],
"assertions": [
"preserve returned user id in returnValue and ArchestrAUserToIdReply",
"preserve invalid user GUID HRESULT/status behavior",
"compare raw mapping behavior without normalizing unknown users"
]
}
],
"eventFixtures": [
{
"id": "event.on-data-change.scalar",
"family": "MX_EVENT_FAMILY_ON_DATA_CHANGE",
"status": "planned_fixture",
"captureReferences": [
"captures/003-subscribe-scalars/harness.log",
"captures/106-native-subscribe-testint-current/harness.log"
],
"assertions": [
"preserve value, quality, timestamp, status array, and worker sequence"
]
},
{
"id": "event.on-write-complete.status",
"family": "MX_EVENT_FAMILY_ON_WRITE_COMPLETE",
"status": "planned_fixture",
"captureReferences": [
"captures/008-write-test-int-same-value/harness.log",
"captures/107-native-write-testint-current/harness.log"
],
"assertions": [
"preserve write-complete status array and optional HRESULT"
]
},
{
"id": "event.operation-complete.native-trigger-gap",
"family": "MX_EVENT_FAMILY_OPERATION_COMPLETE",
"status": "documented_gap",
"captureReferences": [
"captures/077-frida-suspend-advised-scanstate/harness.log",
"captures/118-frida-suspend-advised-scanstate-long/harness.log"
],
"assertions": [
"do not synthesize OperationComplete from Write or OnWriteComplete",
"upgrade this gap when a public MXAccess trigger emits event family 3"
]
},
{
"id": "event.on-buffered-data-change.batch-gap",
"family": "MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE",
"status": "documented_gap",
"captureReferences": [
"captures/120-frida-buffered-history-testhistoryvalue/harness.log",
"captures/122-frida-buffered-history-testhistoryvalue-plainadvise/harness.log"
],
"assertions": [
"preserve raw buffered metadata until a public multi-sample event payload is observed",
"upgrade this gap when OnBufferedDataChange batches are captured from MXAccess"
]
}
],
"scenarioGroups": [
{
"id": "invalid_handles",
"description": "Invalid server, item, post-remove, and invalid-reference cases keep MXAccess-owned HRESULT and status behavior.",
"fixtureIds": [
"method.add-item.scalar",
"method.remove-item.basic",
"method.unadvise.basic",
"method.write.value-status-matrix",
"method.unregister.basic"
],
"captureReferences": [
"captures/006-add-invalid/harness.log",
"captures/007-subscribe-invalid/harness.log",
"captures/109-native-post-remove-errors/harness.log",
"captures/110-native-invalid-handle-errors/harness.log"
]
},
{
"id": "write_statuses",
"description": "Write success, wrong type, invalid reference, scalar arrays, and completion-status cases compare HRESULT, status array, value projection, and event shape.",
"fixtureIds": [
"method.write.value-status-matrix",
"method.write2.timestamped",
"event.on-write-complete.status"
],
"captureReferences": [
"captures/089-frida-write-testint-wrong-type/harness.log",
"captures/090-frida-write-invalid-reference/harness.log",
"captures/091-frida-write-testint-double-type/harness.log",
"captures/097-frida-write-bool-array-pattern/harness.log",
"captures/107-native-write-testint-current/harness.log"
]
},
{
"id": "secured_writes",
"description": "Secured writes include observed WriteSecured rejection and authenticated WriteSecured2 success paths without logging credential-bearing values.",
"fixtureIds": [
"method.write-secured.rejection-gap",
"method.write-secured2.authenticated",
"method.authenticate-user.basic"
],
"captureReferences": [
"captures/036-frida-write-secured-test-int/harness.log",
"captures/111-frida-write-secured-auth-protectedvalue/harness.log",
"captures/113-frida-write-secured2-auth-protectedvalue/harness.log",
"captures/117-frida-write-secured2-auth-testint/harness.log"
]
},
{
"id": "add_item_context",
"description": "Context-bearing item registration compares AddItem2 and buffered AddBufferedItem argument preservation.",
"fixtureIds": [
"method.add-item2.context",
"method.add-buffered-item.context"
],
"captureReferences": [
"captures/mxaccess-additem2-testint-context.log",
"captures/121-frida-buffered-history-testhistoryvalue-context/harness.log"
]
},
{
"id": "buffered_registration",
"description": "Buffered registration and interval setup are tracked separately from normal advice until a public buffered data-change batch is captured.",
"fixtureIds": [
"method.add-buffered-item.context",
"method.set-buffered-update-interval.basic",
"event.on-buffered-data-change.batch-gap"
],
"captureReferences": [
"captures/079-frida-add-buffered-advise-testint/harness.log",
"captures/120-frida-buffered-history-testhistoryvalue/harness.log",
"captures/122-frida-buffered-history-testhistoryvalue-plainadvise/harness.log"
]
}
]
}
+8
View File
@@ -76,6 +76,13 @@ stdout/stderr lines emitted during the run.
## Focused Commands ## Focused Commands
Run the parity fixture matrix tests after changing the integration parity
scenario list:
```bash
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter FullyQualifiedName~ParityFixtureMatrixTests
```
Run the fake worker tests after changing gateway worker IPC, session startup, or Run the fake worker tests after changing gateway worker IPC, session startup, or
event streaming behavior: event streaming behavior:
@@ -95,6 +102,7 @@ dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj
## Related Documentation ## Related Documentation
- [Parity Fixture Matrix](./ParityFixtureMatrix.md)
- [Gateway Process Design](./gateway-process-design.md) - [Gateway Process Design](./gateway-process-design.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md) - [Worker Frame Protocol](./WorkerFrameProtocol.md)
- [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md) - [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md)
+102
View File
@@ -0,0 +1,102 @@
# Parity Fixture Matrix
The parity fixture matrix defines the live-test scenarios used to compare
direct MXAccess behavior with the gateway-backed worker. It is a planning and
validation fixture, not a source of synthetic MXAccess behavior.
The matrix lives in
`clients/proto/fixtures/parity/parity-fixture-matrix.json`. It references the
local MXAccess capture set under
`C:/Users/dohertj2/Desktop/mxaccess/captures` and keeps capture paths relative
to that root so the repository does not copy raw capture artifacts.
## Scope
The matrix covers every public `LMXProxyServerClass` method represented by the
gateway contract:
- `Register`
- `Unregister`
- `AddItem`
- `AddItem2`
- `RemoveItem`
- `Advise`
- `UnAdvise`
- `AdviseSupervisory`
- `AddBufferedItem`
- `SetBufferedUpdateInterval`
- `Suspend`
- `Activate`
- `Write`
- `Write2`
- `WriteSecured`
- `WriteSecured2`
- `AuthenticateUser`
- `ArchestrAUserToId`
Each entry is either a `planned_fixture` or a `documented_gap`.
`WriteSecured` remains a documented gap because the current captures show
`0x80004021` before MXAccess emits a value-bearing write body.
`OperationComplete` and public `OnBufferedDataChange` batches also remain
documented gaps because no capture in the current set proves those public event
payloads from native MXAccess.
## Required Scenario Groups
The matrix pins the high-risk parity scenarios from the integration milestone:
| Scenario | Purpose |
|----------|---------|
| `invalid_handles` | Preserves invalid server, item, post-remove, and invalid-reference HRESULT/status behavior. |
| `write_statuses` | Compares successful writes, wrong-type writes, invalid references, arrays, and write-complete status arrays. |
| `secured_writes` | Covers observed `WriteSecured` rejection and authenticated `WriteSecured2` paths without logging credential-bearing values. |
| `add_item_context` | Ensures `AddItem2` and buffered registration pass context strings exactly as supplied. |
| `buffered_registration` | Tracks buffered item registration and interval setup separately from normal advice. |
## Comparison Format
Each live parity fixture should record one direct MXAccess result and one
gateway result for the same operation.
Direct MXAccess records include:
- method name,
- arguments after redaction,
- returned value,
- HRESULT,
- exception type,
- `MXSTATUS_PROXY[]` values,
- native event records in observed order.
Gateway records include:
- `MxCommandKind`,
- `ProtocolStatus`,
- `MxCommandReply.ReturnValue`,
- `MxCommandReply.Hresult`,
- repeated `MxCommandReply.Statuses`,
- safe diagnostic message,
- streamed `MxEvent` records in worker-sequence order.
Compare HRESULT, exception type, returned value, status array shape, raw status
fields, event family order, event payload shape, value projection, and raw
fallback metadata. The gateway must not convert an MXAccess command failure
into a transport failure when the worker captured HRESULT or status details.
## Validation
Run the parity fixture matrix tests after changing the matrix:
```bash
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter FullyQualifiedName~ParityFixtureMatrixTests
```
Live MXAccess execution remains opt-in. The matrix defines which scenarios to
run when the installed MXAccess COM component and provider state are available;
normal unit tests only validate the repository fixture shape.
## Related Documentation
- [Gateway Testing](./GatewayTesting.md)
- [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md)
- [Protobuf Contracts](./Contracts.md)
+11 -3
View File
@@ -137,9 +137,17 @@ The Python scaffold provides a repo-local generation script:
clients/python/generate-proto.ps1 clients/python/generate-proto.ps1
``` ```
Java clients should use the Gradle protobuf plugin and write generated sources Java clients use the Gradle protobuf plugin from `clients/java`. The
under `clients/java/src/main/generated`. The Java client scaffold owns the `mxgateway-client` project reads the shared `.proto` files and writes generated
Gradle plugin versions and source-set wiring. Java protobuf and gRPC sources under `clients/java/src/main/generated`, matching
the manifest output path. Handwritten client and CLI code stays in the
`mxgateway-client` and `mxgateway-cli` project source trees.
Run the Java workspace checks from `clients/java`:
```powershell
gradle test
```
## Golden Fixtures ## Golden Fixtures
+15
View File
@@ -17,6 +17,7 @@ Recommended Gradle multi-project layout:
clients/java/ clients/java/
settings.gradle settings.gradle
build.gradle build.gradle
src/main/generated/
mxgateway-client/ mxgateway-client/
build.gradle build.gradle
src/main/java/com/dohertylan/mxgateway/client/ src/main/java/com/dohertylan/mxgateway/client/
@@ -31,6 +32,7 @@ Alternative Maven layout is acceptable if the repo standardizes on Maven.
Target Java: Target Java:
- Java 21 recommended. - Java 21 recommended.
- The Gradle scaffold uses the Java 21 toolchain for compilation and tests.
Expected dependencies: Expected dependencies:
@@ -189,3 +191,16 @@ Publish library and CLI separately:
Generated protobuf code should be produced during the build from shared proto Generated protobuf code should be produced during the build from shared proto
files and should not be hand-edited. files and should not be hand-edited.
## Current Build
Run the Java scaffold checks from `clients/java`:
```powershell
gradle test
```
The `mxgateway-client` project generates the gateway and worker protobuf/gRPC
bindings into `src/main/generated`, compiles the generated contracts, and runs
JUnit 5 tests. The `mxgateway-cli` project builds a Picocli-based `mxgw-java`
entry point for later command implementation.
@@ -0,0 +1,293 @@
using System.Text.Json;
using MxGateway.Contracts;
namespace MxGateway.Tests.Contracts;
public sealed class ParityFixtureMatrixTests
{
[Fact]
public void Matrix_DeclaresCurrentProtocolVersionsAndComparisonFields()
{
using JsonDocument matrix = LoadParityMatrix();
JsonElement root = matrix.RootElement;
Assert.Equal(1, root.GetProperty("schemaVersion").GetInt32());
Assert.Equal("mxaccess-gateway-parity-fixture-matrix", root.GetProperty("fixtureSet").GetString());
Assert.Equal(GatewayContractInfo.GatewayProtocolVersion, root.GetProperty("gatewayProtocolVersion").GetUInt32());
Assert.Equal(GatewayContractInfo.WorkerProtocolVersion, root.GetProperty("workerProtocolVersion").GetUInt32());
JsonElement comparisonFormat = root.GetProperty("comparisonFormat");
AssertRequiredFields(
comparisonFormat.GetProperty("directMxAccess").GetProperty("requiredFields"),
"method",
"arguments",
"returnedValue",
"hresult",
"statuses",
"events");
AssertRequiredFields(
comparisonFormat.GetProperty("gatewayResult").GetProperty("requiredFields"),
"kind",
"protocolStatus",
"returnValue",
"hresult",
"statuses",
"events");
AssertRequiredFields(
comparisonFormat.GetProperty("eventFields"),
"family",
"value",
"quality",
"sourceTimestamp",
"statuses",
"workerSequence");
AssertRequiredFields(
comparisonFormat.GetProperty("comparisonKeys"),
"hresult",
"statusArrayShape",
"statusRawFields",
"eventFamilyOrder",
"eventPayloadShape",
"valueProjection",
"rawFallbackMetadata");
}
[Fact]
public void Matrix_CoversEveryPublicMxAccessMethod()
{
using JsonDocument matrix = LoadParityMatrix();
JsonElement methodFixtures = matrix.RootElement.GetProperty("methodFixtures");
Dictionary<string, JsonElement> fixturesByMethod = [];
HashSet<string> ids = new(StringComparer.Ordinal);
foreach (JsonElement fixture in methodFixtures.EnumerateArray())
{
string id = fixture.GetProperty("id").GetString()!;
string method = fixture.GetProperty("method").GetString()!;
string commandKind = fixture.GetProperty("commandKind").GetString()!;
string status = fixture.GetProperty("status").GetString()!;
Assert.True(ids.Add(id), $"Duplicate parity fixture id '{id}'.");
Assert.True(fixturesByMethod.TryAdd(method, fixture), $"Duplicate parity method '{method}'.");
Assert.StartsWith("MX_COMMAND_KIND_", commandKind, StringComparison.Ordinal);
Assert.Contains(status, KnownFixtureStatuses);
Assert.NotEmpty(fixture.GetProperty("assertions").EnumerateArray());
AssertCaptureReferencesAreRelative(fixture.GetProperty("captureReferences"));
}
Assert.Equal(ExpectedPublicMethods.Order(StringComparer.Ordinal), fixturesByMethod.Keys.Order(StringComparer.Ordinal));
foreach (string method in ExpectedPublicMethods)
{
JsonElement fixture = fixturesByMethod[method];
string status = fixture.GetProperty("status").GetString()!;
Assert.True(
status == "planned_fixture" || status == "documented_gap",
$"Method '{method}' must have a planned parity fixture or documented gap.");
}
}
[Fact]
public void Matrix_CoversRequiredParityScenarioGroups()
{
using JsonDocument matrix = LoadParityMatrix();
HashSet<string> knownFixtureIds = GetFixtureIds(matrix.RootElement);
Dictionary<string, JsonElement> groupsById = [];
foreach (JsonElement group in matrix.RootElement.GetProperty("scenarioGroups").EnumerateArray())
{
string id = group.GetProperty("id").GetString()!;
Assert.True(groupsById.TryAdd(id, group), $"Duplicate parity scenario group '{id}'.");
Assert.NotEmpty(group.GetProperty("description").GetString()!);
Assert.NotEmpty(group.GetProperty("fixtureIds").EnumerateArray());
AssertCaptureReferencesAreRelative(group.GetProperty("captureReferences"));
foreach (JsonElement fixtureIdElement in group.GetProperty("fixtureIds").EnumerateArray())
{
string fixtureId = fixtureIdElement.GetString()!;
Assert.Contains(fixtureId, knownFixtureIds);
}
}
foreach (string requiredGroup in RequiredScenarioGroups)
{
Assert.True(groupsById.ContainsKey(requiredGroup), $"Missing required parity scenario group '{requiredGroup}'.");
}
AssertScenarioCovers(groupsById["invalid_handles"], "method.remove-item.basic", "method.write.value-status-matrix");
AssertScenarioCovers(groupsById["write_statuses"], "method.write.value-status-matrix", "event.on-write-complete.status");
AssertScenarioCovers(groupsById["secured_writes"], "method.write-secured.rejection-gap", "method.write-secured2.authenticated");
AssertScenarioCovers(groupsById["add_item_context"], "method.add-item2.context", "method.add-buffered-item.context");
AssertScenarioCovers(groupsById["buffered_registration"], "method.add-buffered-item.context", "event.on-buffered-data-change.batch-gap");
}
[Fact]
public void Matrix_CoversEveryPublicMxAccessEventFamily()
{
using JsonDocument matrix = LoadParityMatrix();
Dictionary<string, JsonElement> fixturesByFamily = [];
foreach (JsonElement fixture in matrix.RootElement.GetProperty("eventFixtures").EnumerateArray())
{
string family = fixture.GetProperty("family").GetString()!;
string status = fixture.GetProperty("status").GetString()!;
Assert.True(fixturesByFamily.TryAdd(family, fixture), $"Duplicate parity event family '{family}'.");
Assert.Contains(status, KnownFixtureStatuses);
Assert.NotEmpty(fixture.GetProperty("assertions").EnumerateArray());
AssertCaptureReferencesAreRelative(fixture.GetProperty("captureReferences"));
}
foreach (string eventFamily in ExpectedEventFamilies)
{
Assert.True(fixturesByFamily.ContainsKey(eventFamily), $"Missing parity fixture for event family '{eventFamily}'.");
}
Assert.Equal("documented_gap", fixturesByFamily["MX_EVENT_FAMILY_OPERATION_COMPLETE"].GetProperty("status").GetString());
Assert.Equal("documented_gap", fixturesByFamily["MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE"].GetProperty("status").GetString());
}
private static readonly string[] ExpectedPublicMethods =
[
"Register",
"Unregister",
"AddItem",
"AddItem2",
"RemoveItem",
"Advise",
"UnAdvise",
"AdviseSupervisory",
"AddBufferedItem",
"SetBufferedUpdateInterval",
"Suspend",
"Activate",
"Write",
"Write2",
"WriteSecured",
"WriteSecured2",
"AuthenticateUser",
"ArchestrAUserToId",
];
private static readonly string[] ExpectedEventFamilies =
[
"MX_EVENT_FAMILY_ON_DATA_CHANGE",
"MX_EVENT_FAMILY_ON_WRITE_COMPLETE",
"MX_EVENT_FAMILY_OPERATION_COMPLETE",
"MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE",
];
private static readonly string[] RequiredScenarioGroups =
[
"invalid_handles",
"write_statuses",
"secured_writes",
"add_item_context",
"buffered_registration",
];
private static readonly string[] KnownFixtureStatuses =
[
"planned_fixture",
"documented_gap",
];
private static void AssertRequiredFields(
JsonElement fields,
params string[] expectedFields)
{
HashSet<string> declared = fields
.EnumerateArray()
.Select(field => field.GetString()!)
.ToHashSet(StringComparer.Ordinal);
foreach (string expectedField in expectedFields)
{
Assert.Contains(expectedField, declared);
}
}
private static void AssertCaptureReferencesAreRelative(JsonElement captureReferences)
{
int count = 0;
foreach (JsonElement captureReference in captureReferences.EnumerateArray())
{
string path = captureReference.GetString()!;
Assert.StartsWith("captures/", path, StringComparison.Ordinal);
Assert.DoesNotContain("\\", path, StringComparison.Ordinal);
Assert.False(Path.IsPathRooted(path), $"Capture reference '{path}' must be relative.");
count++;
}
Assert.True(count > 0, "Each parity fixture must reference at least one MXAccess capture.");
}
private static void AssertScenarioCovers(
JsonElement group,
params string[] fixtureIds)
{
HashSet<string> declared = group
.GetProperty("fixtureIds")
.EnumerateArray()
.Select(fixtureId => fixtureId.GetString()!)
.ToHashSet(StringComparer.Ordinal);
foreach (string fixtureId in fixtureIds)
{
Assert.Contains(fixtureId, declared);
}
}
private static HashSet<string> GetFixtureIds(JsonElement root)
{
HashSet<string> ids = new(StringComparer.Ordinal);
foreach (JsonElement fixture in root.GetProperty("methodFixtures").EnumerateArray())
{
ids.Add(fixture.GetProperty("id").GetString()!);
}
foreach (JsonElement fixture in root.GetProperty("eventFixtures").EnumerateArray())
{
ids.Add(fixture.GetProperty("id").GetString()!);
}
return ids;
}
private static JsonDocument LoadParityMatrix()
{
return JsonDocument.Parse(File.ReadAllText(Path.Combine(GetParityFixtureRoot().FullName, "parity-fixture-matrix.json")));
}
private static DirectoryInfo GetParityFixtureRoot()
{
DirectoryInfo repositoryRoot = FindRepositoryRoot();
return new DirectoryInfo(Path.Combine(repositoryRoot.FullName, "clients", "proto", "fixtures", "parity"));
}
private static DirectoryInfo FindRepositoryRoot()
{
DirectoryInfo? current = new(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "AGENTS.md"))
&& Directory.Exists(Path.Combine(current.FullName, "src"))
&& Directory.Exists(Path.Combine(current.FullName, "clients")))
{
return current;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Could not locate the repository root from the test output directory.");
}
}