fix(clients): resolve 2026-06-18 array-write review findings

- Client.Dotnet-030: add advise-supervisory to IsKnownGatewayCommand (was dead/unreachable, exit 2)
- Client.Go-035/036/037: usage+README list advise-supervisory; add session-id guard test; fix write2 README wording
- Client.Python-037/038: drop regressed 'scaffold' from pyproject; add advise-supervisory CLI tests
- Client.Rust-039/040: document write_array_elements/advise-supervisory in design doc; pin outer MxValue data_type==0
- Client.Java-049/050/051: sync CLIENT_VERSION to 0.1.2; add advise-supervisory test; guard negative uint32 inputs (pending windev gradle verification)

Client READMEs also updated for Server-057 add-family normalization wording.
.NET/Go/Python/Rust verified green locally; Java pending windev.
This commit is contained in:
Joseph Doherty
2026-06-18 10:58:33 -04:00
parent 85ef453d0d
commit 6c853b43af
22 changed files with 404 additions and 43 deletions
+4 -4
View File
@@ -196,10 +196,10 @@ the existing attribute value.
#### Bare-name array AddItem normalisation
`AddItem` for a bare array attribute name (e.g. `Tank01.Temperature`) is
automatically normalised to `Tank01.Temperature[]` by the gateway so the
worker can resolve the full array. You do not need to append `[]` in client
code; the gateway handles it.
Adding a bare array attribute name (e.g. `Tank01.Temperature`) via `AddItem`,
`AddItem2`, `AddItemBulk`, or `AddBufferedItem` is automatically normalised to
`Tank01.Temperature[]` by the gateway so the worker can resolve the full array.
You do not need to append `[]` in client code; the gateway handles it.
## Galaxy Repository browse
+2
View File
@@ -121,6 +121,7 @@ impl Session {
pub async fn read_bulk<S: AsRef<str>>(&self, server_handle: i32, tag_addresses: &[S], timeout_ms: u32) -> Result<Vec<BulkReadResult>, Error>;
pub async fn write(&self, server_handle: i32, item_handle: i32, value: MxValue, user_id: i32) -> Result<(), Error>;
pub async fn write2(&self, server_handle: i32, item_handle: i32, value: MxValue, timestamp_value: MxValue, user_id: i32) -> Result<(), Error>;
pub async fn write_array_elements(&self, server_handle: i32, item_handle: i32, element_data_type: MxDataType, total_length: u32, elements: impl IntoIterator<Item = (u32, MxValue)>, user_id: i32) -> Result<(), Error>;
pub async fn write_bulk(&self, server_handle: i32, entries: Vec<WriteBulkEntry>) -> Result<Vec<BulkWriteResult>, Error>;
pub async fn write2_bulk(&self, server_handle: i32, entries: Vec<Write2BulkEntry>) -> Result<Vec<BulkWriteResult>, Error>;
pub async fn write_secured_bulk(&self, server_handle: i32, entries: Vec<WriteSecuredBulkEntry>) -> Result<Vec<BulkWriteResult>, Error>;
@@ -333,6 +334,7 @@ mxgw close-session --session-id <id>
mxgw register --session-id <id>
mxgw add-item --session-id <id> --server-handle <h> --item <tag>
mxgw advise --session-id <id> --server-handle <h> --item-handle <h>
mxgw advise-supervisory --session-id <id> --server-handle <h> --item-handle <h>
mxgw subscribe-bulk --session-id <id> --server-handle <h> --items <csv>
mxgw unsubscribe-bulk --session-id <id> --server-handle <h> --item-handles <csv>
mxgw read-bulk --session-id <id> --server-handle <h> --items <csv> [--timeout-ms <ms>]
+16 -1
View File
@@ -1212,7 +1212,9 @@ fn sparse_int32_value(
.collect();
MxValue {
data_type: MxDataType::Integer as i32,
// outer data_type must be 0 (Unspecified); the element type lives only
// inside MxSparseArray.element_data_type, matching the
// `..ProtoMxValue::default()` used in Session::write_array_elements.
variant_type: String::new(),
kind: Some(Kind::SparseArrayValue(MxSparseArray {
element_data_type: MxDataType::Integer as i32,
@@ -1227,6 +1229,11 @@ fn sparse_int32_value(
fn write_array_elements_proto_shape_has_sparse_oneof_kind() {
let proto = sparse_int32_value(5, [(0, 10), (3, 30)]);
assert_eq!(
proto.data_type, 0,
"outer MxValue.data_type must be 0 (Unspecified); element type lives in element_data_type"
);
let Kind::SparseArrayValue(ref sparse) = proto.kind.as_ref().unwrap() else {
panic!("expected SparseArrayValue kind, got {:?}", proto.kind);
};
@@ -1253,6 +1260,10 @@ fn write_array_elements_proto_shape_has_sparse_oneof_kind() {
#[test]
fn write_array_elements_empty_elements_is_valid_all_defaults() {
let proto = sparse_int32_value(8, []);
assert_eq!(
proto.data_type, 0,
"outer MxValue.data_type must be 0 (Unspecified) even with no elements"
);
let Kind::SparseArrayValue(ref sparse) = proto.kind.as_ref().unwrap() else {
panic!("expected SparseArrayValue kind");
};
@@ -1269,6 +1280,10 @@ fn sparse_array_value_round_trips_through_client_mx_value_projection_as_unset()
// (e.g. a future version bug), the projection should degrade to Unset
// rather than panic, because the enum variant is not readable.
let proto = sparse_int32_value(4, [(1, 99)]);
assert_eq!(
proto.data_type, 0,
"outer MxValue.data_type must be 0 (Unspecified)"
);
let client_value = ClientMxValue::from_proto(proto);
assert_eq!(
client_value.projection(),