Architecture remediation: P1 tier (process & hardening) #121
@@ -13,6 +13,11 @@ keywords = ["mxaccess", "mxgateway", "grpc", "client", "archestra"]
|
|||||||
categories = ["api-bindings", "asynchronous"]
|
categories = ["api-bindings", "asynchronous"]
|
||||||
publish = ["dohertj2-gitea"]
|
publish = ["dohertj2-gitea"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
# Ship the vendored `.proto` files so `build.rs` can regenerate the tonic/prost
|
||||||
|
# bindings from the packaged tarball — a consumer building the published crate
|
||||||
|
# has no access to the in-repo Contracts protos. See CLI-02. Cargo.toml, the
|
||||||
|
# README, and the license are included automatically.
|
||||||
|
include = ["src/**/*.rs", "protos/*.proto", "build.rs", "README.md"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/mxgw-cli"]
|
members = ["crates/mxgw-cli"]
|
||||||
|
|||||||
+17
-3
@@ -6,11 +6,25 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
configure_protoc();
|
configure_protoc();
|
||||||
|
|
||||||
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
||||||
let repo_root = manifest_dir
|
|
||||||
|
// Resolve the proto source directory. In-repo builds prefer the canonical
|
||||||
|
// Contracts protos (two levels above clients/rust) so local `.proto` edits
|
||||||
|
// are picked up live. When that path is absent — e.g. a consumer building
|
||||||
|
// the crate unpacked from a published tarball — fall back to the vendored
|
||||||
|
// copies shipped in `clients/rust/protos/` (see build.rs staleness note and
|
||||||
|
// CLI-02 in archreview/remediation/50-clients.md). Vendored copies are
|
||||||
|
// build inputs only; the canonical source remains in Contracts and must be
|
||||||
|
// refreshed here whenever the `.proto` files change.
|
||||||
|
let repo_proto_root = manifest_dir
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(Path::parent)
|
.and_then(Path::parent)
|
||||||
.ok_or("clients/rust must live two levels below the repository root")?;
|
.map(|repo_root| repo_root.join("src/ZB.MOM.WW.MxGateway.Contracts/Protos"));
|
||||||
let proto_root = repo_root.join("src/ZB.MOM.WW.MxGateway.Contracts/Protos");
|
let vendored_proto_root = manifest_dir.join("protos");
|
||||||
|
let proto_root = match repo_proto_root {
|
||||||
|
Some(path) if path.is_dir() => path,
|
||||||
|
_ => vendored_proto_root,
|
||||||
|
};
|
||||||
|
|
||||||
let gateway_proto = proto_root.join("mxaccess_gateway.proto");
|
let gateway_proto = proto_root.join("mxaccess_gateway.proto");
|
||||||
let worker_proto = proto_root.join("mxaccess_worker.proto");
|
let worker_proto = proto_root.join("mxaccess_worker.proto");
|
||||||
let galaxy_proto = proto_root.join("galaxy_repository.proto");
|
let galaxy_proto = proto_root.join("galaxy_repository.proto");
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package galaxy_repository.v1;
|
||||||
|
|
||||||
|
option csharp_namespace = "ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
|
||||||
|
// Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||||
|
// additively only. Never renumber or repurpose an existing field number or
|
||||||
|
// enum value. When a field or enum value is removed, add a `reserved` range
|
||||||
|
// (and `reserved` name) covering it in the same change so a future editor
|
||||||
|
// cannot accidentally reuse the retired tag. There are no `reserved`
|
||||||
|
// declarations today because no field or enum value has ever been removed.
|
||||||
|
|
||||||
|
// Read-only browse over the AVEVA System Platform Galaxy Repository (ZB SQL
|
||||||
|
// database). Lets clients enumerate the deployed object hierarchy and each
|
||||||
|
// object's dynamic attributes so they know what tag references to subscribe
|
||||||
|
// to via the MxAccessGateway service.
|
||||||
|
service GalaxyRepository {
|
||||||
|
rpc TestConnection(TestConnectionRequest) returns (TestConnectionReply);
|
||||||
|
rpc GetLastDeployTime(GetLastDeployTimeRequest) returns (GetLastDeployTimeReply);
|
||||||
|
rpc DiscoverHierarchy(DiscoverHierarchyRequest) returns (DiscoverHierarchyReply);
|
||||||
|
|
||||||
|
// Server-stream of deploy events. The server emits the current state immediately
|
||||||
|
// on subscribe (so clients can bootstrap their cache without waiting for the next
|
||||||
|
// deploy), then emits one event each time the gateway's hierarchy cache observes
|
||||||
|
// a new galaxy.time_of_last_deploy. The sequence field is monotonically
|
||||||
|
// increasing per server start; gaps indicate the per-subscriber buffer dropped
|
||||||
|
// older events because the client was too slow.
|
||||||
|
rpc WatchDeployEvents(WatchDeployEventsRequest) returns (stream DeployEvent);
|
||||||
|
|
||||||
|
// Returns the direct children of a parent object (or the root objects when
|
||||||
|
// `parent` is unset). Designed for OPC UA-style lazy expand: clients walk
|
||||||
|
// one level at a time instead of paging the full hierarchy. Filters mirror
|
||||||
|
// DiscoverHierarchy exactly. Backed by the same shared hierarchy cache.
|
||||||
|
rpc BrowseChildren(BrowseChildrenRequest) returns (BrowseChildrenReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
message TestConnectionRequest {}
|
||||||
|
|
||||||
|
message TestConnectionReply {
|
||||||
|
bool ok = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetLastDeployTimeRequest {}
|
||||||
|
|
||||||
|
message GetLastDeployTimeReply {
|
||||||
|
bool present = 1;
|
||||||
|
google.protobuf.Timestamp time_of_last_deploy = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DiscoverHierarchyRequest {
|
||||||
|
// Maximum number of objects to return. The server applies its default when
|
||||||
|
// unset and rejects non-positive values.
|
||||||
|
int32 page_size = 1;
|
||||||
|
// Opaque token returned by a previous DiscoverHierarchy response.
|
||||||
|
string page_token = 2;
|
||||||
|
// Optional. When set, return only this object and its descendants.
|
||||||
|
// Empty = full hierarchy.
|
||||||
|
oneof root {
|
||||||
|
int32 root_gobject_id = 3;
|
||||||
|
string root_tag_name = 4;
|
||||||
|
string root_contained_path = 5;
|
||||||
|
}
|
||||||
|
// Optional. Cap on descendant depth from root. Zero returns only the root.
|
||||||
|
// Unset means unlimited depth.
|
||||||
|
google.protobuf.Int32Value max_depth = 6;
|
||||||
|
// Optional object category id filters.
|
||||||
|
repeated int32 category_ids = 7;
|
||||||
|
// Optional case-insensitive substring filters against template names.
|
||||||
|
repeated string template_chain_contains = 8;
|
||||||
|
// Optional anchored, case-insensitive glob over object tag_name.
|
||||||
|
string tag_name_glob = 9;
|
||||||
|
// Optional. Unset or true includes attributes. False returns object skeletons.
|
||||||
|
optional bool include_attributes = 10;
|
||||||
|
// Optional. Return only objects with at least one alarm-bearing attribute.
|
||||||
|
bool alarm_bearing_only = 11;
|
||||||
|
// Optional. Return only objects with at least one historized attribute.
|
||||||
|
bool historized_only = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DiscoverHierarchyReply {
|
||||||
|
repeated GalaxyObject objects = 1;
|
||||||
|
// Non-empty when another page is available.
|
||||||
|
string next_page_token = 2;
|
||||||
|
// Total number of objects in the cached hierarchy at the time of the call.
|
||||||
|
int32 total_object_count = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WatchDeployEventsRequest {
|
||||||
|
// Optional. When set, the bootstrap event is suppressed if the cached deploy
|
||||||
|
// time matches this value. Future events are still emitted normally.
|
||||||
|
google.protobuf.Timestamp last_seen_deploy_time = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeployEvent {
|
||||||
|
// Monotonically increasing per server start. Gaps indicate dropped events.
|
||||||
|
uint64 sequence = 1;
|
||||||
|
// Server wall-clock when the cache observed the deploy.
|
||||||
|
google.protobuf.Timestamp observed_at = 2;
|
||||||
|
// Galaxy.time_of_last_deploy. Absent only when the Galaxy table reports null.
|
||||||
|
google.protobuf.Timestamp time_of_last_deploy = 3;
|
||||||
|
bool time_of_last_deploy_present = 4;
|
||||||
|
int32 object_count = 5;
|
||||||
|
int32 attribute_count = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GalaxyObject {
|
||||||
|
int32 gobject_id = 1;
|
||||||
|
string tag_name = 2;
|
||||||
|
string contained_name = 3;
|
||||||
|
string browse_name = 4;
|
||||||
|
int32 parent_gobject_id = 5;
|
||||||
|
bool is_area = 6;
|
||||||
|
int32 category_id = 7;
|
||||||
|
int32 hosted_by_gobject_id = 8;
|
||||||
|
repeated string template_chain = 9;
|
||||||
|
repeated GalaxyAttribute attributes = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GalaxyAttribute {
|
||||||
|
string attribute_name = 1;
|
||||||
|
string full_tag_reference = 2;
|
||||||
|
// Raw Galaxy SQL `dbo.data_type` identifier, passed through unchanged.
|
||||||
|
// This is NOT a member of `mxaccess_gateway.v1.MxDataType` — Galaxy's
|
||||||
|
// type enumeration is distinct from MXAccess's wire data-type enum and
|
||||||
|
// the two must not be cast or compared. The GalaxyRepository service is
|
||||||
|
// metadata-only and deliberately does not share types with
|
||||||
|
// mxaccess_gateway.proto. See docs/GalaxyRepository.md.
|
||||||
|
int32 mx_data_type = 3;
|
||||||
|
// Human-readable name from Galaxy's `dbo.data_type` table (e.g. "Float",
|
||||||
|
// "Integer", "Boolean"). Free-form Galaxy text; not a stable enum.
|
||||||
|
string data_type_name = 4;
|
||||||
|
bool is_array = 5;
|
||||||
|
int32 array_dimension = 6;
|
||||||
|
bool array_dimension_present = 7;
|
||||||
|
// Raw Galaxy SQL attribute-category identifier, passed through unchanged.
|
||||||
|
// Galaxy-specific; not mapped to any gateway enum. See
|
||||||
|
// docs/GalaxyRepository.md.
|
||||||
|
int32 mx_attribute_category = 8;
|
||||||
|
// Raw Galaxy SQL security-classification identifier, passed through
|
||||||
|
// unchanged. Galaxy-specific; not mapped to any gateway enum. See
|
||||||
|
// docs/GalaxyRepository.md.
|
||||||
|
int32 security_classification = 9;
|
||||||
|
bool is_historized = 10;
|
||||||
|
bool is_alarm = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BrowseChildrenRequest {
|
||||||
|
// Parent selector. Empty oneof returns root objects (parent_gobject_id == 0).
|
||||||
|
oneof parent {
|
||||||
|
int32 parent_gobject_id = 1;
|
||||||
|
string parent_tag_name = 2;
|
||||||
|
string parent_contained_path = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum number of direct children to return. Server default 500; cap 5000.
|
||||||
|
int32 page_size = 4;
|
||||||
|
// Opaque token returned by a previous BrowseChildren response. Bound to the
|
||||||
|
// cache sequence, parent selector, and the filter set; a mismatch returns
|
||||||
|
// InvalidArgument.
|
||||||
|
string page_token = 5;
|
||||||
|
|
||||||
|
// --- Filter parity with DiscoverHierarchy. AND-combined. ---
|
||||||
|
repeated int32 category_ids = 6;
|
||||||
|
repeated string template_chain_contains = 7;
|
||||||
|
string tag_name_glob = 8;
|
||||||
|
optional bool include_attributes = 9;
|
||||||
|
bool alarm_bearing_only = 10;
|
||||||
|
bool historized_only = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BrowseChildrenReply {
|
||||||
|
// Direct children matching the filter, sorted areas-first then by
|
||||||
|
// case-insensitive display name (same order as the dashboard tree).
|
||||||
|
repeated GalaxyObject children = 1;
|
||||||
|
// Non-empty when another page of siblings is available.
|
||||||
|
string next_page_token = 2;
|
||||||
|
// Total matching direct children of the parent (post-filter).
|
||||||
|
int32 total_child_count = 3;
|
||||||
|
// Parallel array, indexed with `children`. True when the child has at least
|
||||||
|
// one matching descendant under the same filter set. Lets a UI choose
|
||||||
|
// whether to draw an expand triangle without an extra round trip.
|
||||||
|
repeated bool child_has_children = 4;
|
||||||
|
// Cache sequence this reply was projected from. Clients may pass it back as
|
||||||
|
// part of the page_token contract. Mismatch on the next page -> InvalidArgument.
|
||||||
|
uint64 cache_sequence = 5;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package mxaccess_worker.v1;
|
||||||
|
|
||||||
|
option csharp_namespace = "ZB.MOM.WW.MxGateway.Contracts.Proto";
|
||||||
|
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "mxaccess_gateway.proto";
|
||||||
|
|
||||||
|
// Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
|
||||||
|
// additively only. Never renumber or repurpose an existing field number or
|
||||||
|
// enum value. When a field or enum value is removed, add a `reserved` range
|
||||||
|
// (and `reserved` name) covering it in the same change so a future editor
|
||||||
|
// cannot accidentally reuse the retired tag. There are no `reserved`
|
||||||
|
// declarations today because no field or enum value has ever been removed.
|
||||||
|
|
||||||
|
// Gateway-to-worker IPC envelope. Named-pipe framing prepends a little-endian
|
||||||
|
// uint32 payload length to this protobuf payload.
|
||||||
|
message WorkerEnvelope {
|
||||||
|
uint32 protocol_version = 1;
|
||||||
|
string session_id = 2;
|
||||||
|
uint64 sequence = 3;
|
||||||
|
string correlation_id = 4;
|
||||||
|
|
||||||
|
oneof body {
|
||||||
|
GatewayHello gateway_hello = 10;
|
||||||
|
WorkerHello worker_hello = 11;
|
||||||
|
WorkerReady worker_ready = 12;
|
||||||
|
WorkerCommand worker_command = 13;
|
||||||
|
WorkerCommandReply worker_command_reply = 14;
|
||||||
|
WorkerCancel worker_cancel = 15;
|
||||||
|
WorkerShutdown worker_shutdown = 16;
|
||||||
|
WorkerShutdownAck worker_shutdown_ack = 17;
|
||||||
|
WorkerEvent worker_event = 18;
|
||||||
|
WorkerHeartbeat worker_heartbeat = 19;
|
||||||
|
WorkerFault worker_fault = 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GatewayHello {
|
||||||
|
uint32 supported_protocol_version = 1;
|
||||||
|
string nonce = 2;
|
||||||
|
string gateway_version = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerHello {
|
||||||
|
uint32 protocol_version = 1;
|
||||||
|
string nonce = 2;
|
||||||
|
int32 worker_process_id = 3;
|
||||||
|
string worker_version = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerReady {
|
||||||
|
int32 worker_process_id = 1;
|
||||||
|
string mxaccess_progid = 2;
|
||||||
|
string mxaccess_clsid = 3;
|
||||||
|
google.protobuf.Timestamp ready_timestamp = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerCommand {
|
||||||
|
mxaccess_gateway.v1.MxCommand command = 1;
|
||||||
|
google.protobuf.Timestamp enqueue_timestamp = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerCommandReply {
|
||||||
|
mxaccess_gateway.v1.MxCommandReply reply = 1;
|
||||||
|
google.protobuf.Timestamp completed_timestamp = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerCancel {
|
||||||
|
string reason = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerShutdown {
|
||||||
|
google.protobuf.Duration grace_period = 1;
|
||||||
|
string reason = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerShutdownAck {
|
||||||
|
mxaccess_gateway.v1.ProtocolStatus status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerEvent {
|
||||||
|
mxaccess_gateway.v1.MxEvent event = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerHeartbeat {
|
||||||
|
int32 worker_process_id = 1;
|
||||||
|
WorkerState state = 2;
|
||||||
|
google.protobuf.Timestamp last_sta_activity_timestamp = 3;
|
||||||
|
uint32 pending_command_count = 4;
|
||||||
|
uint32 outbound_event_queue_depth = 5;
|
||||||
|
uint64 last_event_sequence = 6;
|
||||||
|
string current_command_correlation_id = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WorkerFault {
|
||||||
|
WorkerFaultCategory category = 1;
|
||||||
|
string command_method = 2;
|
||||||
|
optional int32 hresult = 3;
|
||||||
|
string exception_type = 4;
|
||||||
|
string diagnostic_message = 5;
|
||||||
|
mxaccess_gateway.v1.ProtocolStatus protocol_status = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WorkerState {
|
||||||
|
WORKER_STATE_UNSPECIFIED = 0;
|
||||||
|
WORKER_STATE_STARTING = 1;
|
||||||
|
WORKER_STATE_HANDSHAKING = 2;
|
||||||
|
WORKER_STATE_INITIALIZING_STA = 3;
|
||||||
|
WORKER_STATE_READY = 4;
|
||||||
|
WORKER_STATE_EXECUTING_COMMAND = 5;
|
||||||
|
WORKER_STATE_SHUTTING_DOWN = 6;
|
||||||
|
WORKER_STATE_STOPPED = 7;
|
||||||
|
WORKER_STATE_FAULTED = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WorkerFaultCategory {
|
||||||
|
WORKER_FAULT_CATEGORY_UNSPECIFIED = 0;
|
||||||
|
WORKER_FAULT_CATEGORY_INVALID_ARGUMENTS = 1;
|
||||||
|
WORKER_FAULT_CATEGORY_GATEWAY_AUTHENTICATION_FAILED = 2;
|
||||||
|
WORKER_FAULT_CATEGORY_PROTOCOL_MISMATCH = 3;
|
||||||
|
WORKER_FAULT_CATEGORY_PROTOCOL_VIOLATION = 4;
|
||||||
|
WORKER_FAULT_CATEGORY_PIPE_DISCONNECTED = 5;
|
||||||
|
WORKER_FAULT_CATEGORY_MXACCESS_CREATION_FAILED = 6;
|
||||||
|
WORKER_FAULT_CATEGORY_MXACCESS_COMMAND_FAILED = 7;
|
||||||
|
WORKER_FAULT_CATEGORY_MXACCESS_EVENT_CONVERSION_FAILED = 8;
|
||||||
|
WORKER_FAULT_CATEGORY_STA_HUNG = 9;
|
||||||
|
WORKER_FAULT_CATEGORY_QUEUE_OVERFLOW = 10;
|
||||||
|
WORKER_FAULT_CATEGORY_SHUTDOWN_TIMEOUT = 11;
|
||||||
|
}
|
||||||
@@ -187,7 +187,9 @@ function Invoke-PackRust {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Write-Host 'Running cargo package...'
|
Write-Host 'Running cargo package...'
|
||||||
& cargo package --no-verify
|
# No --no-verify: the crate vendors its protos (clients/rust/protos) so
|
||||||
|
# the verify build proves it compiles standalone from the tarball (CLI-02).
|
||||||
|
& cargo package
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'cargo package failed.' }
|
if ($LASTEXITCODE -ne 0) { throw 'cargo package failed.' }
|
||||||
|
|
||||||
$packageDir = Join-Path $rustDir 'target/package'
|
$packageDir = Join-Path $rustDir 'target/package'
|
||||||
@@ -207,7 +209,7 @@ function Invoke-PackRust {
|
|||||||
Write-Host 'Publishing Rust crate to Gitea...' -ForegroundColor Yellow
|
Write-Host 'Publishing Rust crate to Gitea...' -ForegroundColor Yellow
|
||||||
Push-Location (Join-Path $RepoRoot 'clients/rust')
|
Push-Location (Join-Path $RepoRoot 'clients/rust')
|
||||||
try {
|
try {
|
||||||
& cargo publish --no-verify --registry dohertj2-gitea
|
& cargo publish --registry dohertj2-gitea
|
||||||
if ($LASTEXITCODE -ne 0) { throw 'cargo publish failed.' }
|
if ($LASTEXITCODE -ne 0) { throw 'cargo publish failed.' }
|
||||||
} finally { Pop-Location }
|
} finally { Pop-Location }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user