diff --git a/clients/dotnet/README.md b/clients/dotnet/README.md index 87455aa..1625e0c 100644 --- a/clients/dotnet/README.md +++ b/clients/dotnet/README.md @@ -121,6 +121,53 @@ can keep the full `MxCommandReply`, HRESULT, and status array when MXAccess itself rejects a command. `MxAccessException.Reply` contains the raw generated reply. +## Write Semantics And Common Pitfalls + +These are MXAccess parity behaviors that surprise new callers. The gateway +forwards them unchanged — it does not paper over them. + +### Attributing a write to a user without `AuthenticateUser` + +MXAccess only stamps a plain `Write`/`Write2` with a Galaxy user id when the +item carries an active *supervisory* advise. If you are **not** using the +verified/secured path (`AuthenticateUser` → `WriteSecured`/`WriteSecured2`) but +still need the write attributed to a user id, you must first advise the item +supervisory and then pass that user id on the write. Without the supervisory +advise the `userId` on a plain write is ignored. + +The library exposes `Advise`/`UnAdvise` as named helpers but not supervisory +advise, so send it through the generic command channel: + +```csharp +await session.InvokeAsync(new MxCommandRequest +{ + SessionId = session.SessionId, + Command = new MxCommand + { + Kind = MxCommandKind.AdviseSupervisory, + AdviseSupervisory = new AdviseSupervisoryCommand + { + ServerHandle = serverHandle, + ItemHandle = itemHandle, + }, + }, +}); + +await session.WriteAsync(serverHandle, itemHandle, value.ToMxValue(), userId); +``` + +The CLI exposes the same command as `advise-supervisory`, and `write` / +`write2` take `--user-id`. + +### Array writes replace the whole array + +A write to an array attribute **replaces the entire array**; it is not an +element-wise patch. To change a subset of elements, send the full array with +the unchanged elements included. For example, to change 2 elements of a +20-element array, build the `MxValue` from all 20 values (the 18 unchanged plus +the 2 new ones). Sending only the 2 changed values overwrites the attribute +with a 2-element array. + ## CLI Usage The test CLI supports deterministic JSON output for automation: diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs index e163420..93e69e1 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Cli/MxGatewayClientCli.cs @@ -110,6 +110,8 @@ public static class MxGatewayClientCli .ConfigureAwait(false), "advise" => await AdviseAsync(arguments, client, standardOutput, cancellation.Token) .ConfigureAwait(false), + "advise-supervisory" => await AdviseSupervisoryAsync(arguments, client, standardOutput, cancellation.Token) + .ConfigureAwait(false), "subscribe-bulk" => await SubscribeBulkAsync(arguments, client, standardOutput, cancellation.Token) .ConfigureAwait(false), "unsubscribe-bulk" => await UnsubscribeBulkAsync(arguments, client, standardOutput, cancellation.Token) @@ -451,6 +453,28 @@ public static class MxGatewayClientCli cancellationToken); } + private static Task AdviseSupervisoryAsync( + CliArguments arguments, + IMxGatewayCliClient client, + TextWriter output, + CancellationToken cancellationToken) + { + return InvokeAndWriteAsync( + arguments, + client, + output, + new MxCommand + { + Kind = MxCommandKind.AdviseSupervisory, + AdviseSupervisory = new AdviseSupervisoryCommand + { + ServerHandle = arguments.GetInt32("server-handle"), + ItemHandle = arguments.GetInt32("item-handle"), + }, + }, + cancellationToken); + } + private static Task SubscribeBulkAsync( CliArguments arguments, IMxGatewayCliClient client, @@ -2066,6 +2090,7 @@ public static class MxGatewayClientCli writer.WriteLine("mxgw-dotnet register --session-id --client-name [--json]"); writer.WriteLine("mxgw-dotnet add-item --session-id --server-handle --item [--json]"); writer.WriteLine("mxgw-dotnet advise --session-id --server-handle --item-handle [--json]"); + writer.WriteLine("mxgw-dotnet advise-supervisory --session-id --server-handle --item-handle [--json]"); writer.WriteLine("mxgw-dotnet subscribe-bulk --session-id --server-handle --items [--json]"); writer.WriteLine("mxgw-dotnet unsubscribe-bulk --session-id --server-handle --item-handles [--json]"); writer.WriteLine("mxgw-dotnet read-bulk --session-id --server-handle --items [--timeout-ms ] [--json]"); diff --git a/clients/go/README.md b/clients/go/README.md index 9aa0da8..fa0b33d 100644 --- a/clients/go/README.md +++ b/clients/go/README.md @@ -99,6 +99,52 @@ call returns a `StreamAlarmsClient`; cancel its context to terminate the stream. All three pass straight through to the gateway's central alarm monitor. +## Write Semantics And Common Pitfalls + +These are MXAccess parity behaviors that surprise new callers. The gateway +forwards them unchanged — it does not paper over them. + +### Attributing a write to a user without `AuthenticateUser` + +MXAccess only stamps a plain `Write`/`Write2` with a Galaxy user id when the +item carries an active *supervisory* advise. If you are **not** using the +verified/secured path (`AuthenticateUser` → `WriteSecured`/`WriteSecured2`) but +still need the write attributed to a user id, you must first advise the item +supervisory and then pass that user id on the write. Without the supervisory +advise the `userID` on a plain write is ignored. + +The session exposes `Advise`/`UnAdvise` but not supervisory advise, so send it +through the generic command channel: + +```go +_, err := client.Invoke(ctx, &pb.MxCommandRequest{ + SessionId: session.ID(), + Command: &pb.MxCommand{ + Kind: pb.MxCommandKind_MX_COMMAND_KIND_ADVISE_SUPERVISORY, + Payload: &pb.MxCommand_AdviseSupervisory{ + AdviseSupervisory: &pb.AdviseSupervisoryCommand{ + ServerHandle: serverHandle, + ItemHandle: itemHandle, + }, + }, + }, +}) + +err = session.Write(ctx, serverHandle, itemHandle, value, userID) +``` + +The CLI exposes the same command as `advise-supervisory`, and `write` / +`write2` take `--user-id`. + +### Array writes replace the whole array + +A write to an array attribute **replaces the entire array**; it is not an +element-wise patch. To change a subset of elements, send the full array with +the unchanged elements included. For example, to change 2 elements of a +20-element array, build the `MxValue` from all 20 values (the 18 unchanged plus +the 2 new ones). Sending only the 2 changed values overwrites the attribute +with a 2-element array. + ## Galaxy Repository browse The `GalaxyRepository` service (proto package `galaxy_repository.v1`) is a diff --git a/clients/go/cmd/mxgw-go/main.go b/clients/go/cmd/mxgw-go/main.go index 11fadb1..d2dfda1 100644 --- a/clients/go/cmd/mxgw-go/main.go +++ b/clients/go/cmd/mxgw-go/main.go @@ -21,6 +21,7 @@ import ( "syscall" "time" + pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated" "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/mxgateway" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/reflect/protoreflect" @@ -87,6 +88,8 @@ func runWithIO(ctx context.Context, args []string, stdout, stderr io.Writer) err return runAddItem(ctx, args[1:], stdout, stderr) case "advise": return runAdvise(ctx, args[1:], stdout, stderr) + case "advise-supervisory": + return runAdviseSupervisory(ctx, args[1:], stdout, stderr) case "subscribe-bulk": return runSubscribeBulk(ctx, args[1:], stdout, stderr) case "unsubscribe-bulk": @@ -358,6 +361,43 @@ func runAdvise(ctx context.Context, args []string, stdout, stderr io.Writer) err return writeCommandOutput(stdout, *jsonOutput, "advise", options, reply, err) } +func runAdviseSupervisory(ctx context.Context, args []string, stdout, stderr io.Writer) error { + flags := flag.NewFlagSet("advise-supervisory", flag.ContinueOnError) + flags.SetOutput(stderr) + common := bindCommonFlags(flags) + jsonOutput := flags.Bool("json", false, "write JSON output") + sessionID := flags.String("session-id", "", "gateway session id") + serverHandle := flags.Int("server-handle", 0, "MXAccess server handle") + itemHandle := flags.Int("item-handle", 0, "MXAccess item handle") + + if err := flags.Parse(args); err != nil { + return err + } + if *sessionID == "" { + return errors.New("session-id is required") + } + + client, options, err := dialForCommand(ctx, common) + if err != nil { + return err + } + defer client.Close() + + reply, err := client.Invoke(ctx, &pb.MxCommandRequest{ + SessionId: *sessionID, + Command: &pb.MxCommand{ + Kind: pb.MxCommandKind_MX_COMMAND_KIND_ADVISE_SUPERVISORY, + Payload: &pb.MxCommand_AdviseSupervisory{ + AdviseSupervisory: &pb.AdviseSupervisoryCommand{ + ServerHandle: int32(*serverHandle), + ItemHandle: int32(*itemHandle), + }, + }, + }, + }) + return writeCommandOutput(stdout, *jsonOutput, "advise-supervisory", options, reply, err) +} + func runSubscribeBulk(ctx context.Context, args []string, stdout, stderr io.Writer) error { flags := flag.NewFlagSet("subscribe-bulk", flag.ContinueOnError) flags.SetOutput(stderr) diff --git a/clients/java/README.md b/clients/java/README.md index d94d11a..04e50de 100644 --- a/clients/java/README.md +++ b/clients/java/README.md @@ -84,6 +84,46 @@ yields alarm-feed messages from the gateway's central monitor), and `acknowledgeAlarm` (ack by full alarm reference with an optional comment and ack target). Close the subscription to cancel the underlying gRPC stream. +## Write Semantics And Common Pitfalls + +These are MXAccess parity behaviors that surprise new callers. The gateway +forwards them unchanged — it does not paper over them. + +### Attributing a write to a user without `authenticateUser` + +MXAccess only stamps a plain `write`/`write2` with a Galaxy user id when the +item carries an active *supervisory* advise. If you are **not** using the +verified/secured path (`authenticateUser` → `writeSecured`/`writeSecured2`) but +still need the write attributed to a user id, you must first advise the item +supervisory and then pass that user id on the write. Without the supervisory +advise the `userId` on a plain write is ignored. + +The session exposes `advise`/`unAdvise` but not supervisory advise, so send it +through the generic command channel: + +```java +session.invokeCommand(MxCommand.newBuilder() + .setKind(MxCommandKind.MX_COMMAND_KIND_ADVISE_SUPERVISORY) + .setAdviseSupervisory(AdviseSupervisoryCommand.newBuilder() + .setServerHandle(serverHandle) + .setItemHandle(itemHandle)) + .build()); + +session.write(serverHandle, itemHandle, value, userId); +``` + +The CLI exposes the same command as `advise-supervisory`, and `write` / +`write2` take `--user-id`. + +### Array writes replace the whole array + +A write to an array attribute **replaces the entire array**; it is not an +element-wise patch. To change a subset of elements, send the full array with +the unchanged elements included. For example, to change 2 elements of a +20-element array, build the `MxValue` from all 20 values (the 18 unchanged plus +the 2 new ones). Sending only the 2 changed values overwrites the attribute +with a 2-element array. + ## Galaxy Repository Browse The Galaxy Repository service is a separate metadata-only gRPC service exposed diff --git a/clients/java/zb-mom-ww-mxgateway-cli/src/main/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCli.java b/clients/java/zb-mom-ww-mxgateway-cli/src/main/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCli.java index 44b4483..32ef7a5 100644 --- a/clients/java/zb-mom-ww-mxgateway-cli/src/main/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCli.java +++ b/clients/java/zb-mom-ww-mxgateway-cli/src/main/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCli.java @@ -153,6 +153,8 @@ public final class MxGatewayCli implements Callable { commandLine.addSubcommand("register", new RegisterCommand(clientFactory)); commandLine.addSubcommand("add-item", new AddItemCommand(clientFactory)); commandLine.addSubcommand("advise", new AdviseCommand(clientFactory)); + commandLine.addSubcommand( + "advise-supervisory", new AdviseSupervisoryCommand(clientFactory)); commandLine.addSubcommand("subscribe-bulk", new SubscribeBulkCommand(clientFactory)); commandLine.addSubcommand("unsubscribe-bulk", new UnsubscribeBulkCommand(clientFactory)); commandLine.addSubcommand("read-bulk", new ReadBulkCommand(clientFactory)); @@ -1044,6 +1046,34 @@ public final class MxGatewayCli implements Callable { } } + @Command( + name = "advise-supervisory", + description = "Invokes MXAccess AdviseSupervisory.") + static final class AdviseSupervisoryCommand extends GatewayCommand { + @Option(names = "--session-id", required = true, description = "Gateway session id.") + String sessionId; + + @Option(names = "--server-handle", required = true, description = "MXAccess server handle.") + int serverHandle; + + @Option(names = "--item-handle", required = true, description = "MXAccess item handle.") + int itemHandle; + + AdviseSupervisoryCommand(MxGatewayCliClientFactory clientFactory) { + super(clientFactory); + } + + @Override + public Integer call() { + try (MxGatewayCliClient client = clientFactory.connect(common.resolved())) { + MxCommandReply reply = + client.session(sessionId).adviseSupervisoryRaw(serverHandle, itemHandle); + writeOutput("advise-supervisory", common, json, reply, () -> reply.getKind().name()); + } + return 0; + } + } + @Command(name = "subscribe-bulk", description = "Invokes MXAccess SubscribeBulk.") static final class SubscribeBulkCommand extends GatewayCommand { @Option(names = "--session-id", required = true, description = "Gateway session id.") @@ -1830,6 +1860,8 @@ public final class MxGatewayCli implements Callable { MxCommandReply adviseRaw(int serverHandle, int itemHandle); + MxCommandReply adviseSupervisoryRaw(int serverHandle, int itemHandle); + MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId); List subscribeBulk(int serverHandle, List items); @@ -1948,6 +1980,17 @@ public final class MxGatewayCli implements Callable { return session.adviseRaw(serverHandle, itemHandle); } + @Override + public MxCommandReply adviseSupervisoryRaw(int serverHandle, int itemHandle) { + return session.invokeCommand(MxCommand.newBuilder() + .setKind(MxCommandKind.MX_COMMAND_KIND_ADVISE_SUPERVISORY) + .setAdviseSupervisory( + mxaccess_gateway.v1.MxaccessGateway.AdviseSupervisoryCommand.newBuilder() + .setServerHandle(serverHandle) + .setItemHandle(itemHandle)) + .build()); + } + @Override public MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId) { return session.writeRaw(serverHandle, itemHandle, value, userId); diff --git a/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java b/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java index fc2c2c5..6c31a5e 100644 --- a/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java +++ b/clients/java/zb-mom-ww-mxgateway-cli/src/test/java/com/zb/mom/ww/mxgateway/cli/MxGatewayCliTests.java @@ -1302,6 +1302,15 @@ final class MxGatewayCliTests { .build(); } + @Override + public MxCommandReply adviseSupervisoryRaw(int serverHandle, int itemHandle) { + adviseCalled = true; + return MxCommandReply.newBuilder() + .setKind(MxCommandKind.MX_COMMAND_KIND_ADVISE_SUPERVISORY) + .setProtocolStatus(ok()) + .build(); + } + @Override public MxCommandReply writeRaw(int serverHandle, int itemHandle, MxValue value, int userId) { lastWriteValue = value; diff --git a/clients/python/README.md b/clients/python/README.md index f340e3e..4ad1e84 100644 --- a/clients/python/README.md +++ b/clients/python/README.md @@ -105,6 +105,49 @@ terminate the stream. Canceling a Python task cancels the client-side gRPC call or stream wait. It does not abort an in-flight MXAccess COM call inside the worker process. +## Write Semantics And Common Pitfalls + +These are MXAccess parity behaviors that surprise new callers. The gateway +forwards them unchanged — it does not paper over them. + +### Attributing a write to a user without `authenticate_user` + +MXAccess only stamps a plain `write`/`write2` with a Galaxy user id when the +item carries an active *supervisory* advise. If you are **not** using the +verified/secured path (`authenticate_user` → `write_secured`/`write_secured2`) +but still need the write attributed to a user id, you must first advise the +item supervisory and then pass that user id on the write. Without the +supervisory advise the `user_id` on a plain write is ignored. + +The session exposes `advise`/`unadvise` but not supervisory advise, so send it +through the generic command channel: + +```python +await session.invoke( + pb.MxCommand( + kind=pb.MX_COMMAND_KIND_ADVISE_SUPERVISORY, + advise_supervisory=pb.AdviseSupervisoryCommand( + server_handle=server_handle, + item_handle=item_handle, + ), + ) +) + +await session.write(server_handle, item_handle, value, user_id=user_id) +``` + +The CLI exposes the same command as `advise-supervisory`, and `write` / +`write2` take `--user-id`. + +### Array writes replace the whole array + +A write to an array attribute **replaces the entire array**; it is not an +element-wise patch. To change a subset of elements, send the full array with +the unchanged elements included. For example, to change 2 elements of a +20-element array, build the `MxValue` from all 20 values (the 18 unchanged plus +the 2 new ones). Sending only the 2 changed values overwrites the attribute +with a 2-element array. + ## Galaxy Repository Browse The `GalaxyRepositoryClient` wraps the read-only `GalaxyRepository` gRPC diff --git a/clients/python/src/zb_mom_ww_mxgateway_cli/commands.py b/clients/python/src/zb_mom_ww_mxgateway_cli/commands.py index 97da512..99876ad 100644 --- a/clients/python/src/zb_mom_ww_mxgateway_cli/commands.py +++ b/clients/python/src/zb_mom_ww_mxgateway_cli/commands.py @@ -277,6 +277,23 @@ def advise(**kwargs: Any) -> None: _run(_advise(**kwargs), output_json=kwargs["output_json"], secrets=_secrets(kwargs)) +@main.command("advise-supervisory") +@gateway_options +@click.option("--session-id", required=True, help="Gateway session id.") +@click.option("--server-handle", required=True, type=int, help="MXAccess server handle.") +@click.option("--item-handle", required=True, type=int, help="MXAccess item handle.") +@click.option("--correlation-id", default="", help="Client correlation id.") +@click.option("--json", "output_json", is_flag=True, help="Emit JSON output.") +def advise_supervisory(**kwargs: Any) -> None: + """Invoke MXAccess AdviseSupervisory.""" + + _run( + _advise_supervisory(**kwargs), + output_json=kwargs["output_json"], + secrets=_secrets(kwargs), + ) + + @main.command("subscribe-bulk") @gateway_options @click.option("--session-id", required=True, help="Gateway session id.") @@ -725,6 +742,22 @@ async def _advise(**kwargs: Any) -> dict[str, Any]: return {"ok": True} +async def _advise_supervisory(**kwargs: Any) -> dict[str, Any]: + async with await _connect(kwargs) as client: + session = _session(client, kwargs["session_id"]) + await session.invoke( + pb.MxCommand( + kind=pb.MX_COMMAND_KIND_ADVISE_SUPERVISORY, + advise_supervisory=pb.AdviseSupervisoryCommand( + server_handle=kwargs["server_handle"], + item_handle=kwargs["item_handle"], + ), + ), + correlation_id=kwargs["correlation_id"], + ) + return {"ok": True} + + async def _subscribe_bulk(**kwargs: Any) -> dict[str, Any]: async with await _connect(kwargs) as client: session = _session(client, kwargs["session_id"]) diff --git a/clients/rust/README.md b/clients/rust/README.md index 27779e9..934898e 100644 --- a/clients/rust/README.md +++ b/clients/rust/README.md @@ -125,6 +125,49 @@ preserving the raw message for parity diagnostics. Command replies whose protocol status is not `PROTOCOL_STATUS_CODE_OK` become `Error::Command` and retain the raw `MxCommandReply`. +## Write Semantics And Common Pitfalls + +These are MXAccess parity behaviors that surprise new callers. The gateway +forwards them unchanged — it does not paper over them. + +### Attributing a write to a user without `authenticate_user` + +MXAccess only stamps a plain `write`/`write2` with a Galaxy user id when the +item carries an active *supervisory* advise. If you are **not** using the +verified/secured path (`authenticate_user` → `write_secured`/`write_secured2`) +but still need the write attributed to a user id, you must first advise the +item supervisory and then pass that user id on the write. Without the +supervisory advise the `user_id` on a plain write is ignored. + +The session exposes `advise`/`un_advise` but not supervisory advise, so send it +through the generic command channel: + +```rust +session + .invoke( + MxCommandKind::AdviseSupervisory, + Payload::AdviseSupervisory(AdviseSupervisoryCommand { + server_handle, + item_handle, + }), + ) + .await?; + +session.write(server_handle, item_handle, value, user_id).await?; +``` + +The CLI exposes the same command as `advise-supervisory`, and `write` / +`write2` take `--user-id`. + +### Array writes replace the whole array + +A write to an array attribute **replaces the entire array**; it is not an +element-wise patch. To change a subset of elements, send the full array with +the unchanged elements included. For example, to change 2 elements of a +20-element array, build the `MxValue` from all 20 values (the 18 unchanged plus +the 2 new ones). Sending only the 2 changed values overwrites the attribute +with a 2-element array. + ## Galaxy Repository browse The Galaxy Repository service exposes a read-only browse over the AVEVA System diff --git a/clients/rust/crates/mxgw-cli/src/main.rs b/clients/rust/crates/mxgw-cli/src/main.rs index 1ea2442..60d7b21 100644 --- a/clients/rust/crates/mxgw-cli/src/main.rs +++ b/clients/rust/crates/mxgw-cli/src/main.rs @@ -21,10 +21,11 @@ use serde_json::Value; use zb_mom_ww_mxgateway_client::galaxy::{BrowseChildrenOptions, LazyBrowseNode}; use zb_mom_ww_mxgateway_client::generated::galaxy_repository::v1::DeployEvent; use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::{ - alarm_feed_message, AcknowledgeAlarmRequest, AlarmFeedMessage, CloseSessionRequest, MxCommand, - MxCommandKind, MxCommandRequest, MxEvent, MxEventFamily, MxValue as ProtoMxValue, - OpenSessionRequest, PingCommand, StreamAlarmsRequest, StreamEventsRequest, Write2BulkEntry, - WriteBulkEntry, WriteSecured2BulkEntry, WriteSecuredBulkEntry, + alarm_feed_message, AcknowledgeAlarmRequest, AdviseSupervisoryCommand, AlarmFeedMessage, + CloseSessionRequest, MxCommand, MxCommandKind, MxCommandRequest, MxEvent, MxEventFamily, + MxValue as ProtoMxValue, OpenSessionRequest, PingCommand, StreamAlarmsRequest, + StreamEventsRequest, Write2BulkEntry, WriteBulkEntry, WriteSecured2BulkEntry, + WriteSecuredBulkEntry, }; use zb_mom_ww_mxgateway_client::{ next_correlation_id, ApiKey, ClientOptions, Error, GalaxyClient, GatewayClient, MxValue, @@ -105,6 +106,18 @@ enum Command { #[arg(long)] json: bool, }, + AdviseSupervisory { + #[command(flatten)] + connection: ConnectionArgs, + #[arg(long)] + session_id: String, + #[arg(long)] + server_handle: i32, + #[arg(long)] + item_handle: i32, + #[arg(long)] + json: bool, + }, SubscribeBulk { #[command(flatten)] connection: ConnectionArgs, @@ -647,6 +660,27 @@ async fn dispatch(command: Command) -> Result<(), Error> { session.advise(server_handle, item_handle).await?; print_ok("advise", json); } + Command::AdviseSupervisory { + connection, + session_id, + server_handle, + item_handle, + json, + } => { + let session = session_for(connection, session_id).await?; + session + .invoke( + MxCommandKind::AdviseSupervisory, + zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::mx_command::Payload::AdviseSupervisory( + AdviseSupervisoryCommand { + server_handle, + item_handle, + }, + ), + ) + .await?; + print_ok("advise-supervisory", json); + } Command::SubscribeBulk { connection, session_id, diff --git a/gateway.md b/gateway.md index 356a5ef..894885f 100644 --- a/gateway.md +++ b/gateway.md @@ -1049,6 +1049,15 @@ Known important parity areas from existing captures: - Invalid handles and cross-server handles have specific exception/status behavior. - STA message pumping is required for event delivery. +- A plain `Write`/`Write2` only honors its `user_id` when the item has an active + supervisory advise. Callers that do not go through the + `AuthenticateUser` → `WriteSecured`/`WriteSecured2` path must send + `AdviseSupervisory` for the item before a user id on a plain write is + recorded; otherwise the user id is ignored. +- Writing an array attribute replaces the whole array — it is not an + element-wise patch. To change a subset of elements the caller must send the + full array (unchanged elements included); sending only the changed elements + resizes the attribute. The gateway should not "fix" these behaviors unless the client explicitly opts into a non-parity mode.