Add gateway implementation planning docs
This commit is contained in:
+148
@@ -0,0 +1,148 @@
|
||||
# OS files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Editor and IDE state
|
||||
.vs/
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
.idea/
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.rsuser
|
||||
*.DotSettings.user
|
||||
|
||||
# Local environment and secrets
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.local
|
||||
*.secret
|
||||
*.secrets
|
||||
secrets.json
|
||||
|
||||
# Logs and crash dumps
|
||||
*.log
|
||||
logs/
|
||||
*.dmp
|
||||
*.dump
|
||||
*.mdmp
|
||||
|
||||
# Build artifacts
|
||||
artifacts/
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# .NET
|
||||
**/bin/
|
||||
**/obj/
|
||||
TestResults/
|
||||
*.trx
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
coverage/
|
||||
packages/
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
project.lock.json
|
||||
project.assets.json
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Go
|
||||
clients/go/bin/
|
||||
*.test
|
||||
coverage.out
|
||||
coverage.html
|
||||
go.work.sum
|
||||
|
||||
# Rust
|
||||
target/
|
||||
**/target/
|
||||
*.profraw
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.Python
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
.pyre/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
htmlcov/
|
||||
pip-wheel-metadata/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
|
||||
# Java, Maven, and Gradle
|
||||
.gradle/
|
||||
**/target/
|
||||
**/build/
|
||||
*.class
|
||||
*.jar
|
||||
!**/gradle/wrapper/gradle-wrapper.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.nar
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
# Node tooling, used by frontend or documentation tools if added
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
.parcel-cache/
|
||||
.next/
|
||||
.nuxt/
|
||||
.svelte-kit/
|
||||
|
||||
# Protobuf and generated build scratch
|
||||
*.protobin
|
||||
*.protodesc
|
||||
*.pb.tmp
|
||||
generated-scratch/
|
||||
|
||||
# Local database and service state
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
*.bak
|
||||
*.ldf
|
||||
*.mdf
|
||||
|
||||
# Archives and packages produced locally
|
||||
*.zip
|
||||
*.7z
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
*.rar
|
||||
|
||||
# Keep empty directories with .gitkeep files when needed
|
||||
!.gitkeep
|
||||
@@ -7,6 +7,14 @@ without requiring those clients to load MXAccess COM, run as x86, or own an STA
|
||||
message pump. Treat the installed MXAccess COM component as the compatibility
|
||||
baseline.
|
||||
|
||||
Toolchain paths, versions, and external analysis locations are recorded in
|
||||
`docs/toolchain-links.md`. Use that file before searching for compilers,
|
||||
runtimes, protobuf tools, MXAccess notes, or Galaxy Repository SQL notes.
|
||||
|
||||
Implementation planning is recorded in `docs/implementation-plan-index.md`.
|
||||
Follow the order there unless the user explicitly reprioritizes: gateway first,
|
||||
MXAccess worker instance second, clients third.
|
||||
|
||||
## Core Contract
|
||||
|
||||
Preserve MXAccess behavior first:
|
||||
@@ -54,6 +62,25 @@ default design.
|
||||
- Worker process model: one external client session maps to one worker by
|
||||
default.
|
||||
|
||||
## Style Guides
|
||||
|
||||
Follow the project documentation guide and the language guide for every changed
|
||||
area:
|
||||
|
||||
| Area | Style guide |
|
||||
|------|-------------|
|
||||
| Documentation | `StyleGuide.md` |
|
||||
| Gateway, worker, .NET client, and C# tests | `docs/style-guides/CSharpStyleGuide.md` |
|
||||
| Public gRPC and worker IPC contracts | `docs/style-guides/ProtobufStyleGuide.md` |
|
||||
| Go client | `docs/style-guides/GoStyleGuide.md` |
|
||||
| Rust client | `docs/style-guides/RustStyleGuide.md` |
|
||||
| Python client | `docs/style-guides/PythonStyleGuide.md` |
|
||||
| Java client | `docs/style-guides/JavaStyleGuide.md` |
|
||||
|
||||
When a change crosses languages, apply every affected style guide. Generated
|
||||
code follows its generator output; do not hand-edit it to match handwritten
|
||||
style.
|
||||
|
||||
## Expected Layout
|
||||
|
||||
Prefer this structure unless there is a strong reason to adjust it:
|
||||
@@ -70,6 +97,7 @@ src/MxGateway.Server/
|
||||
Sessions/
|
||||
Workers/
|
||||
Grpc/
|
||||
Dashboard/
|
||||
Metrics/
|
||||
|
||||
src/MxGateway.Worker/
|
||||
@@ -90,6 +118,21 @@ src/MxGateway.Worker.Tests/
|
||||
|
||||
src/MxGateway.IntegrationTests/
|
||||
optional live MXAccess tests
|
||||
|
||||
clients/dotnet/
|
||||
.NET 10 C# client library, test CLI, and tests
|
||||
|
||||
clients/go/
|
||||
Go client module, test CLI, and tests
|
||||
|
||||
clients/rust/
|
||||
Rust client crate, test CLI, and tests
|
||||
|
||||
clients/python/
|
||||
Python client package, test CLI, and tests
|
||||
|
||||
clients/java/
|
||||
Java client library, test CLI, and tests
|
||||
```
|
||||
|
||||
The contracts project may multi-target, or the `.proto` files may be shared as
|
||||
@@ -159,6 +202,79 @@ Command replies should include protocol status, COM HRESULT if available,
|
||||
MXAccess return values, method-specific out parameters, and status arrays where
|
||||
the MXAccess method emits them.
|
||||
|
||||
## Galaxy Repository SQL Discovery
|
||||
|
||||
Galaxy tags, hierarchy, and attribute details can be queried from the AVEVA /
|
||||
Wonderware System Platform Galaxy Repository SQL Server database. Use this as a
|
||||
discovery and metadata path only; runtime MXAccess parity still belongs to the
|
||||
MXAccess-backed worker unless an explicit non-parity backend is being designed.
|
||||
|
||||
Full notes, schema details, screenshots, and query examples are in:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\lmxopcua\gr
|
||||
```
|
||||
|
||||
Important files in that notes directory:
|
||||
|
||||
- `connectioninfo.md` - SQL Server connection details and `sqlcmd` usage.
|
||||
- `layout.md` - hierarchy vs `tag_name` relationship.
|
||||
- `build_layout_plan.md` - extraction plan for hierarchy and attributes.
|
||||
- `schema.md` and `ddl/` - Galaxy Repository schema reference.
|
||||
- `queries/hierarchy.sql` - deployed object hierarchy.
|
||||
- `queries/attributes.sql` - user-defined dynamic attributes.
|
||||
- `queries/attributes_extended.sql` - system plus user-defined attributes.
|
||||
- `queries/change_detection.sql` - deployment-change polling via
|
||||
`galaxy.time_of_last_deploy`.
|
||||
|
||||
Current documented connection is SQL Server `localhost`, database `ZB`, Windows
|
||||
Auth. Example:
|
||||
|
||||
```powershell
|
||||
sqlcmd -S localhost -d ZB -E -Q "SELECT time_of_last_deploy FROM galaxy;"
|
||||
```
|
||||
|
||||
Key tables from the notes are `gobject`, `template_definition`,
|
||||
`dynamic_attribute`, `attribute_definition`, `primitive_instance`, and
|
||||
`galaxy`. The hierarchy uses contained names for human-readable browsing, while
|
||||
runtime tag references use globally unique `tag_name` values such as
|
||||
`<tag_name>.<AttributeName>`.
|
||||
|
||||
## MXAccess Analysis Source
|
||||
|
||||
Use the local MXAccess analysis project when answering questions about installed
|
||||
MXAccess classes, interfaces, fields, events, HRESULT/status behavior, value
|
||||
projection, captures, and parity gaps:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\mxaccess
|
||||
```
|
||||
|
||||
Primary files:
|
||||
|
||||
- `README.md` - overview of available analysis and capture artifacts.
|
||||
- `docs/MXAccess-Public-API.md` - COM class, ProgID, CLSID, method list,
|
||||
event signatures, `MxDataType`, `MxStatus`, and `MXSTATUS_PROXY`.
|
||||
- `docs/MXAccess-Reverse-Engineering.md` - installed runtime path and x86 COM
|
||||
constraints.
|
||||
- `docs/Current-Sprint-State.md` and `docs/DotNet10-Native-Library-Plan.md` -
|
||||
current parity gaps and managed native-client research status.
|
||||
- `src/MxTraceHarness/` - x86 MXAccess harness examples using the real COM
|
||||
interop assembly.
|
||||
- `captures/` and `analysis/` - observed native behavior and generated
|
||||
reverse-engineering artifacts.
|
||||
|
||||
Concrete MXAccess COM target from the analysis:
|
||||
|
||||
- class: `ArchestrA.MxAccess.LMXProxyServerClass`
|
||||
- CLSID: `{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
||||
- ProgID: `LMXProxy.LMXProxyServer.1`
|
||||
- version-independent ProgID: `LMXProxy.LMXProxyServer`
|
||||
- registered server: `C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll`
|
||||
- interop assembly:
|
||||
`C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`
|
||||
- threading model: `Apartment`
|
||||
|
||||
## Worker Rules
|
||||
|
||||
Each worker owns:
|
||||
@@ -210,6 +326,7 @@ identity and launched worker identity. Prefer a per-session nonce handshake.
|
||||
The gateway is responsible for:
|
||||
|
||||
- public TCP/gRPC API,
|
||||
- Blazor Server dashboard using Bootstrap CSS/JS only,
|
||||
- authn/authz when needed,
|
||||
- session creation and teardown,
|
||||
- worker launch and lifecycle management,
|
||||
@@ -223,6 +340,11 @@ The gRPC layer should stay thin: validate request, find session, call the
|
||||
session worker client, map worker replies to public replies, and stream events.
|
||||
Keep MXAccess-specific translation logic testable outside the gRPC handlers.
|
||||
|
||||
Dashboard code should also stay thin and read-only for v1. Use a snapshot
|
||||
service over session/worker/metrics state; do not let Razor components mutate
|
||||
gateway sessions or workers directly. Do not use MudBlazor or other Blazor UI
|
||||
component libraries.
|
||||
|
||||
Gateway restart should not try to reattach old workers in the first version.
|
||||
Terminate orphaned workers on startup if that behavior is implemented.
|
||||
|
||||
@@ -305,6 +427,40 @@ Known important parity areas:
|
||||
behavior.
|
||||
- STA message pumping is required for event delivery.
|
||||
|
||||
## Source Update Workflow
|
||||
|
||||
When source code changes, build the affected component before handing work
|
||||
back. If the change crosses component boundaries, build each affected component
|
||||
instead of relying on a single top-level build.
|
||||
|
||||
Use the native build and test command for each changed area:
|
||||
|
||||
| Changed area | Required verification |
|
||||
|--------------|-----------------------|
|
||||
| Contracts or `.proto` files | regenerate generated code, then build gateway, worker, and every generated client touched by the contract |
|
||||
| Gateway server, sessions, workers, gRPC, dashboard, or metrics | build the .NET 10 gateway project and run affected gateway or fake-worker tests |
|
||||
| Worker IPC, STA, MXAccess, or conversion code | build the .NET Framework 4.8 x86 worker project and run affected worker tests |
|
||||
| Shared test infrastructure | run every test suite that consumes the changed helpers |
|
||||
| .NET client | build the .NET client library, CLI, and tests |
|
||||
| Go client | run Go formatting, build, and tests for the Go module |
|
||||
| Rust client | run Rust formatting, build or check, and tests for the Rust crate |
|
||||
| Python client | run Python formatting or linting if configured, package/build checks, and tests |
|
||||
| Java client | build the Java client library, CLI, and tests |
|
||||
| Integration tests | run them only when the required MXAccess COM component, provider state, and external services are available; otherwise document why they were skipped |
|
||||
|
||||
Update affected documentation in the same change as the source update. This
|
||||
includes `gateway.md`, component design docs under `docs/`, client docs, API
|
||||
contract notes, test instructions, and operational guidance. Documentation must
|
||||
follow `StyleGuide.md`: write technical present-tense prose, explain the reason
|
||||
for non-obvious choices, use exact code names, specify languages on code
|
||||
blocks, use relative links for internal docs, and avoid stale temporary notes.
|
||||
Source code and contract changes must also follow the relevant language guide
|
||||
from the Style Guides section.
|
||||
|
||||
Do not leave documentation describing old behavior after changing public APIs,
|
||||
contracts, configuration, build steps, security behavior, event shapes, value
|
||||
conversion, status mapping, lifecycle rules, or client semantics.
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
Build the smallest end-to-end slice first:
|
||||
@@ -323,4 +479,3 @@ Build the smallest end-to-end slice first:
|
||||
|
||||
That slice proves the high-risk requirements: process isolation, STA ownership,
|
||||
message pumping, command routing, and event streaming.
|
||||
|
||||
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
# Documentation Style Guide
|
||||
|
||||
This guide defines writing conventions and formatting rules for all ScadaBridge documentation.
|
||||
|
||||
## Tone and Voice
|
||||
|
||||
### Be Technical and Direct
|
||||
|
||||
Write for developers who are familiar with .NET. Don't explain basic concepts like dependency injection or async/await unless they're used in an unusual way.
|
||||
|
||||
**Good:**
|
||||
> The `ScadaGatewayActor` routes messages to the appropriate `ScadaClientActor` based on the client ID in the message.
|
||||
|
||||
**Avoid:**
|
||||
> The ScadaGatewayActor is a really powerful component that helps manage all your SCADA connections efficiently!
|
||||
|
||||
### Explain "Why" Not Just "What"
|
||||
|
||||
Document the reasoning behind patterns and decisions, not just the mechanics.
|
||||
|
||||
**Good:**
|
||||
> Health checks use a 5-second timeout because actors under heavy load may take several seconds to respond, but longer delays indicate a real problem.
|
||||
|
||||
**Avoid:**
|
||||
> Health checks use a 5-second timeout.
|
||||
|
||||
### Use Present Tense
|
||||
|
||||
Describe what the code does, not what it will do.
|
||||
|
||||
**Good:**
|
||||
> The actor validates the message before processing.
|
||||
|
||||
**Avoid:**
|
||||
> The actor will validate the message before processing.
|
||||
|
||||
### No Marketing Language
|
||||
|
||||
This is internal technical documentation. Avoid superlatives and promotional language.
|
||||
|
||||
**Avoid:** "powerful", "robust", "cutting-edge", "seamless", "blazing fast"
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### File Names
|
||||
|
||||
Use `PascalCase.md` for all documentation files:
|
||||
- `Overview.md`
|
||||
- `HealthChecks.md`
|
||||
- `StateMachines.md`
|
||||
- `SignalR.md`
|
||||
|
||||
### Headings
|
||||
|
||||
- **H1 (`#`):** Document title only, Title Case
|
||||
- **H2 (`##`):** Major sections, Title Case
|
||||
- **H3 (`###`):** Subsections, Sentence case
|
||||
- **H4+ (`####`):** Rarely needed, Sentence case
|
||||
|
||||
```markdown
|
||||
# Actor Health Checks
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Setting the timeout
|
||||
|
||||
#### Default values
|
||||
```
|
||||
|
||||
### Code Blocks
|
||||
|
||||
Always specify the language:
|
||||
|
||||
````markdown
|
||||
```csharp
|
||||
public class MyActor : ReceiveActor { }
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"Setting": "value"
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
dotnet build
|
||||
```
|
||||
````
|
||||
|
||||
Supported languages: `csharp`, `json`, `bash`, `xml`, `sql`, `yaml`, `html`, `css`, `javascript`
|
||||
|
||||
### Code Snippets
|
||||
|
||||
**Length:** 5-25 lines is typical. Shorter for simple concepts, longer for complete examples.
|
||||
|
||||
**Context:** Include enough to understand where the code lives:
|
||||
|
||||
```csharp
|
||||
// Good - shows class context
|
||||
public class TemplateInstanceActor : ReceiveActor
|
||||
{
|
||||
public TemplateInstanceActor(TemplateInstanceConfig config)
|
||||
{
|
||||
Receive<StartProcessing>(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid - orphaned snippet
|
||||
Receive<StartProcessing>(Handle);
|
||||
```
|
||||
|
||||
**Accuracy:** Only use code that exists in the codebase. Never invent examples.
|
||||
|
||||
### Lists
|
||||
|
||||
Use bullet points for unordered items:
|
||||
```markdown
|
||||
- First item
|
||||
- Second item
|
||||
- Third item
|
||||
```
|
||||
|
||||
Use numbers for sequential steps:
|
||||
```markdown
|
||||
1. Do this first
|
||||
2. Then do this
|
||||
3. Finally do this
|
||||
```
|
||||
|
||||
### Tables
|
||||
|
||||
Use tables for structured reference information:
|
||||
|
||||
```markdown
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `Timeout` | `5000` | Milliseconds to wait |
|
||||
| `RetryCount` | `3` | Number of retry attempts |
|
||||
```
|
||||
|
||||
### Inline Code
|
||||
|
||||
Use backticks for:
|
||||
- Class names: `ScadaGatewayActor`
|
||||
- Method names: `HandleMessage()`
|
||||
- File names: `appsettings.json`
|
||||
- Configuration keys: `ScadaBridge:Timeout`
|
||||
- Command-line commands: `dotnet build`
|
||||
|
||||
### Links
|
||||
|
||||
Use relative paths for internal documentation:
|
||||
```markdown
|
||||
[See the Actors guide](../Akka/Actors.md)
|
||||
[Configuration options](./Configuration.md)
|
||||
```
|
||||
|
||||
Use descriptive link text:
|
||||
```markdown
|
||||
<!-- Good -->
|
||||
See the [Actor Health Checks](../Akka/HealthChecks.md) documentation.
|
||||
|
||||
<!-- Avoid -->
|
||||
See [here](../Akka/HealthChecks.md) for more.
|
||||
```
|
||||
|
||||
## Structure Conventions
|
||||
|
||||
### Document Opening
|
||||
|
||||
Every document starts with:
|
||||
1. H1 title
|
||||
2. 1-2 sentence description of purpose
|
||||
|
||||
```markdown
|
||||
# Actor Health Checks
|
||||
|
||||
Health checks monitor actor responsiveness and report status to the ASP.NET Core health check system.
|
||||
```
|
||||
|
||||
### Section Organization
|
||||
|
||||
Organize content from general to specific:
|
||||
1. Overview/introduction
|
||||
2. Key concepts (if needed)
|
||||
3. Basic usage
|
||||
4. Advanced usage
|
||||
5. Configuration
|
||||
6. Troubleshooting
|
||||
7. Related documentation
|
||||
|
||||
### Code Example Placement
|
||||
|
||||
Place code examples immediately after the concept they illustrate:
|
||||
|
||||
```markdown
|
||||
## Message Handling
|
||||
|
||||
Actors process messages using `Receive<T>` handlers:
|
||||
|
||||
```csharp
|
||||
Receive<MyMessage>(msg => HandleMyMessage(msg));
|
||||
```
|
||||
|
||||
Each handler processes one message type...
|
||||
```
|
||||
|
||||
### Related Documentation Section
|
||||
|
||||
End each document with links to related topics:
|
||||
|
||||
```markdown
|
||||
## Related Documentation
|
||||
|
||||
- [Actor Patterns](./Patterns.md)
|
||||
- [Health Checks](../Operations/HealthChecks.md)
|
||||
- [Configuration](../Configuration/Akka.md)
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Match Code Exactly
|
||||
|
||||
Use the exact names from source code:
|
||||
- `TemplateInstanceActor` not "Template Instance Actor"
|
||||
- `ScadaGatewayActor` not "SCADA Gateway Actor"
|
||||
- `IRequiredActor<T>` not "required actor interface"
|
||||
|
||||
### Acronyms
|
||||
|
||||
Spell out on first use, then use acronym:
|
||||
> OPC Unified Architecture (OPC UA) provides industrial communication standards. OPC UA servers expose...
|
||||
|
||||
Common acronyms that don't need expansion:
|
||||
- API
|
||||
- JSON
|
||||
- SQL
|
||||
- HTTP/HTTPS
|
||||
- REST
|
||||
- JWT
|
||||
- UI
|
||||
|
||||
### File Paths
|
||||
|
||||
Use forward slashes and backticks:
|
||||
- `src/Infrastructure/Akka/Actors/`
|
||||
- `appsettings.json`
|
||||
- `Documentation/Akka/Overview.md`
|
||||
|
||||
## What to Avoid
|
||||
|
||||
### Don't Document the Obvious
|
||||
|
||||
```markdown
|
||||
<!-- Avoid -->
|
||||
## Constructor
|
||||
|
||||
The constructor creates a new instance of the class.
|
||||
|
||||
<!-- Better - only document if there's something notable -->
|
||||
## Constructor
|
||||
|
||||
The constructor accepts an `IActorRef` for the gateway actor, which must be resolved before actor creation.
|
||||
```
|
||||
|
||||
### Don't Duplicate Source Code Comments
|
||||
|
||||
If code has good comments, reference the file rather than copying:
|
||||
> See `ScadaGatewayActor.cs` lines 45-60 for the message routing logic.
|
||||
|
||||
### Don't Include Temporary Information
|
||||
|
||||
Avoid dates, version numbers, or "coming soon" notes that will become stale.
|
||||
|
||||
### Don't Over-Explain .NET Basics
|
||||
|
||||
Assume readers know:
|
||||
- Dependency injection
|
||||
- async/await
|
||||
- LINQ
|
||||
- Entity Framework basics
|
||||
- ASP.NET Core middleware pipeline
|
||||
@@ -0,0 +1,389 @@
|
||||
# Client Libraries Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
This document defines the shared design for official MXAccess Gateway gRPC
|
||||
clients. Each supported language should provide:
|
||||
|
||||
- a reusable client library,
|
||||
- a test CLI built on that library,
|
||||
- unit tests that run without a live gateway,
|
||||
- optional integration tests against a live gateway.
|
||||
|
||||
Target client languages:
|
||||
|
||||
- .NET 10 C#
|
||||
- Go
|
||||
- Rust
|
||||
- Python
|
||||
- Java
|
||||
|
||||
Language-specific plans:
|
||||
|
||||
- `docs/clients-dotnet-csharp-design.md`
|
||||
- `docs/clients-golang-design.md`
|
||||
- `docs/clients-rust-design.md`
|
||||
- `docs/clients-python-design.md`
|
||||
- `docs/clients-java-design.md`
|
||||
|
||||
Language style guides:
|
||||
|
||||
| Client | Style guide |
|
||||
|--------|-------------|
|
||||
| .NET C# | [C# Style Guide](./style-guides/CSharpStyleGuide.md) |
|
||||
| Go | [Go Style Guide](./style-guides/GoStyleGuide.md) |
|
||||
| Rust | [Rust Style Guide](./style-guides/RustStyleGuide.md) |
|
||||
| Python | [Python Style Guide](./style-guides/PythonStyleGuide.md) |
|
||||
| Java | [Java Style Guide](./style-guides/JavaStyleGuide.md) |
|
||||
| Generated protobuf/gRPC code | [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md) |
|
||||
|
||||
## Goals
|
||||
|
||||
Client libraries should make the gateway pleasant to consume without hiding
|
||||
MXAccess behavior.
|
||||
|
||||
Goals:
|
||||
|
||||
- expose sessions as first-class objects,
|
||||
- support unary `OpenSession`, `CloseSession`, and `Invoke`,
|
||||
- support server-streaming `StreamEvents`,
|
||||
- attach API key auth metadata to every call,
|
||||
- preserve gateway, worker, COM, HRESULT, and MXAccess status detail,
|
||||
- provide method-specific command helpers,
|
||||
- provide raw command escape hatches for parity work,
|
||||
- provide deterministic test CLIs for smoke and integration testing,
|
||||
- keep generated protobuf/gRPC code separate from handwritten wrappers.
|
||||
|
||||
Non-goals for v1:
|
||||
|
||||
- client-side reconnectable sessions,
|
||||
- client-side event replay,
|
||||
- client-side command batching,
|
||||
- synthetic MXAccess events,
|
||||
- hiding MXAccess handles behind opaque client-only handles.
|
||||
|
||||
## Public Client Concepts
|
||||
|
||||
All languages should expose the same core concepts, using idiomatic naming:
|
||||
|
||||
- gateway client,
|
||||
- session,
|
||||
- command request,
|
||||
- command reply,
|
||||
- event stream,
|
||||
- MXAccess event,
|
||||
- MX value,
|
||||
- MX status proxy,
|
||||
- gateway error,
|
||||
- client options.
|
||||
|
||||
The gateway session id and MXAccess handles must remain visible. The library may
|
||||
offer helper methods, but it must not invent alternate handle semantics.
|
||||
|
||||
## Shared API Shape
|
||||
|
||||
Each language should support this conceptual API:
|
||||
|
||||
```text
|
||||
client = GatewayClient.connect(endpoint, apiKey, options)
|
||||
session = client.openSession(options)
|
||||
|
||||
serverHandle = session.register(clientName)
|
||||
itemHandle = session.addItem(serverHandle, itemReference)
|
||||
session.advise(serverHandle, itemHandle)
|
||||
|
||||
events = session.streamEvents()
|
||||
session.write(serverHandle, itemHandle, value, userId)
|
||||
|
||||
session.close()
|
||||
client.close()
|
||||
```
|
||||
|
||||
Each library should also expose lower-level calls:
|
||||
|
||||
```text
|
||||
client.openSession(rawRequest)
|
||||
client.closeSession(rawRequest)
|
||||
client.invoke(rawCommandRequest)
|
||||
client.streamEvents(rawStreamRequest)
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The gateway uses API key auth for v1. Clients should support:
|
||||
|
||||
```text
|
||||
authorization: Bearer mxgw_<key-id>_<secret>
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not log API keys.
|
||||
- Redact keys in CLI error output.
|
||||
- Allow API key from command line, environment variable, or config object.
|
||||
- Recommended environment variable: `MXGATEWAY_API_KEY`.
|
||||
- Attach auth metadata to every unary and streaming call.
|
||||
- Treat `Unauthenticated` and `PermissionDenied` distinctly.
|
||||
|
||||
## TLS
|
||||
|
||||
Clients should support:
|
||||
|
||||
- plaintext for local development,
|
||||
- TLS with system roots,
|
||||
- TLS with custom CA file,
|
||||
- optional server name override for test environments.
|
||||
|
||||
Default should be secure for packaged production examples, but the test CLI may
|
||||
default to plaintext when endpoint is `localhost` or `127.0.0.1`.
|
||||
|
||||
## Timeouts And Cancellation
|
||||
|
||||
Each client library should support:
|
||||
|
||||
- connect timeout,
|
||||
- unary call timeout,
|
||||
- command timeout passed to gateway when the public API supports it,
|
||||
- stream cancellation,
|
||||
- graceful session close timeout.
|
||||
|
||||
Language wrappers should map cancellation to the native ecosystem:
|
||||
|
||||
- .NET: `CancellationToken`
|
||||
- Go: `context.Context`
|
||||
- Rust: `tokio` cancellation / dropped future plus explicit timeout
|
||||
- Python: `asyncio` task cancellation and deadlines
|
||||
- Java: `Deadline`, `CompletableFuture`, and stream cancellation
|
||||
|
||||
Canceling a client call does not imply the worker COM call was aborted. Client
|
||||
docs and errors must make that clear.
|
||||
|
||||
## Error Model
|
||||
|
||||
Each client should distinguish:
|
||||
|
||||
- transport errors,
|
||||
- authentication/authorization errors,
|
||||
- gateway session errors,
|
||||
- worker process/protocol errors,
|
||||
- MXAccess command failures,
|
||||
- COM HRESULT/status failures.
|
||||
|
||||
Generated gRPC errors should not be the only error surface. The wrapper should
|
||||
return rich command replies when the gateway reached MXAccess and MXAccess
|
||||
returned HRESULT/status information.
|
||||
|
||||
Recommended high-level error categories:
|
||||
|
||||
```text
|
||||
TransportError
|
||||
AuthenticationError
|
||||
AuthorizationError
|
||||
SessionError
|
||||
WorkerError
|
||||
ProtocolError
|
||||
CommandError
|
||||
MxAccessError
|
||||
TimeoutError
|
||||
CancelledError
|
||||
```
|
||||
|
||||
## Values
|
||||
|
||||
Each language should provide ergonomic conversion helpers for `MxValue`:
|
||||
|
||||
- bool,
|
||||
- int32,
|
||||
- int64,
|
||||
- float,
|
||||
- double,
|
||||
- string,
|
||||
- timestamp,
|
||||
- typed arrays,
|
||||
- raw variant fallback.
|
||||
|
||||
The raw protobuf value should always remain accessible.
|
||||
|
||||
Do not lose raw variant metadata when conversion is incomplete. For CLI output,
|
||||
render both typed projection and raw metadata when present.
|
||||
|
||||
## Events
|
||||
|
||||
Each client should expose event streaming as the idiomatic streaming primitive:
|
||||
|
||||
- .NET: `IAsyncEnumerable<MxEvent>`
|
||||
- Go: receive loop over generated stream
|
||||
- Rust: `Stream<Item = Result<MxEvent, Error>>`
|
||||
- Python: async iterator
|
||||
- Java: blocking iterator and async observer variants
|
||||
|
||||
Events must preserve gateway order. Libraries should not reorder, coalesce, or
|
||||
drop events by default.
|
||||
|
||||
The event surface must include:
|
||||
|
||||
- `OnDataChange`
|
||||
- `OnWriteComplete`
|
||||
- `OperationComplete`
|
||||
- `OnBufferedDataChange`
|
||||
- terminal session fault when represented as a message
|
||||
|
||||
`OperationComplete` is forwarded only when native MXAccess raises it.
|
||||
`OnBufferedDataChange` payload conversion may include raw metadata until live
|
||||
multi-sample buffered payloads are fully validated.
|
||||
|
||||
## Test CLI Contract
|
||||
|
||||
Each language should include a test CLI that exercises the library. The CLI is
|
||||
not the production gateway server.
|
||||
|
||||
Required commands:
|
||||
|
||||
```text
|
||||
version
|
||||
ping
|
||||
open-session
|
||||
close-session
|
||||
register
|
||||
add-item
|
||||
advise
|
||||
stream-events
|
||||
write
|
||||
write2
|
||||
smoke
|
||||
```
|
||||
|
||||
Optional commands:
|
||||
|
||||
```text
|
||||
add-item2
|
||||
add-buffered-item
|
||||
set-buffered-update-interval
|
||||
authenticate-user
|
||||
write-secured
|
||||
write-secured2
|
||||
get-worker-info
|
||||
metadata-query
|
||||
```
|
||||
|
||||
Common CLI flags:
|
||||
|
||||
```text
|
||||
--endpoint <host:port or URL>
|
||||
--api-key <key>
|
||||
--api-key-env <name>
|
||||
--plaintext
|
||||
--tls
|
||||
--ca-file <path>
|
||||
--session-id <id>
|
||||
--client-name <name>
|
||||
--server-handle <int>
|
||||
--item-handle <int>
|
||||
--item <reference>
|
||||
--context <context>
|
||||
--value <value>
|
||||
--type <mx type>
|
||||
--timeout <duration>
|
||||
--json
|
||||
--verbose
|
||||
```
|
||||
|
||||
The `smoke` command should:
|
||||
|
||||
1. open a session,
|
||||
2. register a client name,
|
||||
3. add one item,
|
||||
4. advise it,
|
||||
5. optionally write a value,
|
||||
6. stream events for a bounded duration,
|
||||
7. close the session.
|
||||
|
||||
CLI output should support JSON for automated tests.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Unit tests must run without a live gateway. Use fake gRPC services, mock
|
||||
transports, or generated test servers depending on language.
|
||||
|
||||
Required unit test areas:
|
||||
|
||||
- options parsing,
|
||||
- auth metadata injection,
|
||||
- TLS/plaintext channel setup,
|
||||
- method-specific request construction,
|
||||
- value conversion,
|
||||
- status conversion,
|
||||
- command reply error mapping,
|
||||
- stream event iteration,
|
||||
- stream cancellation,
|
||||
- timeout behavior,
|
||||
- CLI argument parsing,
|
||||
- CLI JSON output redaction of secrets.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Integration tests are optional and should be opt-in. They may require a live
|
||||
gateway and installed MXAccess on the gateway host.
|
||||
|
||||
Recommended environment variables:
|
||||
|
||||
```text
|
||||
MXGATEWAY_ENDPOINT
|
||||
MXGATEWAY_API_KEY
|
||||
MXGATEWAY_TEST_ITEM
|
||||
MXGATEWAY_TEST_CONTEXT
|
||||
MXGATEWAY_TEST_WRITE_VALUE
|
||||
MXGATEWAY_INTEGRATION=1
|
||||
```
|
||||
|
||||
Integration tests should skip unless `MXGATEWAY_INTEGRATION=1`.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
Recommended top-level layout:
|
||||
|
||||
```text
|
||||
clients/
|
||||
dotnet/
|
||||
go/
|
||||
rust/
|
||||
python/
|
||||
java/
|
||||
```
|
||||
|
||||
Each client should contain:
|
||||
|
||||
```text
|
||||
src or package source
|
||||
generated protobuf/grpc source
|
||||
test CLI
|
||||
unit tests
|
||||
README.md
|
||||
examples/
|
||||
```
|
||||
|
||||
Generated code should be reproducible from `src/MxGateway.Contracts/Protos/`.
|
||||
Do not hand-edit generated code.
|
||||
|
||||
## Versioning
|
||||
|
||||
All clients should expose:
|
||||
|
||||
- client library version,
|
||||
- supported gateway protocol version,
|
||||
- generated protobuf version if available.
|
||||
|
||||
Version compatibility should be tested against protocol-version mismatch cases.
|
||||
|
||||
## Documentation
|
||||
|
||||
Each client README should include:
|
||||
|
||||
- install instructions,
|
||||
- minimal open/register/add/advise example,
|
||||
- API key configuration,
|
||||
- TLS configuration,
|
||||
- CLI examples,
|
||||
- integration test instructions,
|
||||
- warning that canceling a client call does not abort an in-flight MXAccess COM
|
||||
call.
|
||||
@@ -0,0 +1,193 @@
|
||||
# .NET 10 C# Client Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide an idiomatic .NET 10 C# client library for MXAccess Gateway, plus a test
|
||||
CLI and unit tests. This client is for modern .NET callers and must not load
|
||||
MXAccess COM.
|
||||
|
||||
Follow the [C# Style Guide](./style-guides/CSharpStyleGuide.md) for
|
||||
handwritten code and the [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
|
||||
for generated contract inputs.
|
||||
|
||||
## Projects
|
||||
|
||||
Recommended layout:
|
||||
|
||||
```text
|
||||
clients/dotnet/
|
||||
MxGateway.Client/
|
||||
MxGateway.Client.csproj
|
||||
GatewayClient.cs
|
||||
MxGatewaySession.cs
|
||||
MxGatewayClientOptions.cs
|
||||
Authentication/
|
||||
Conversion/
|
||||
Errors/
|
||||
Generated/
|
||||
MxGateway.Client.Cli/
|
||||
MxGateway.Client.Cli.csproj
|
||||
Program.cs
|
||||
Commands/
|
||||
MxGateway.Client.Tests/
|
||||
MxGateway.Client.Tests.csproj
|
||||
MxGateway.Client.IntegrationTests/
|
||||
MxGateway.Client.IntegrationTests.csproj
|
||||
```
|
||||
|
||||
Target framework:
|
||||
|
||||
```xml
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
```
|
||||
|
||||
Expected packages:
|
||||
|
||||
- `Grpc.Net.Client`
|
||||
- `Google.Protobuf`
|
||||
- `Grpc.Tools` for generation
|
||||
- `Microsoft.Extensions.Logging.Abstractions`
|
||||
- `System.CommandLine` or similar for CLI
|
||||
- test framework: xUnit or NUnit
|
||||
|
||||
## Library API
|
||||
|
||||
Suggested public types:
|
||||
|
||||
```csharp
|
||||
public sealed class MxGatewayClient : IAsyncDisposable
|
||||
{
|
||||
public static MxGatewayClient Create(MxGatewayClientOptions options);
|
||||
public Task<MxGatewaySession> OpenSessionAsync(
|
||||
OpenSessionOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
public Task<MxCommandReply> InvokeAsync(
|
||||
MxCommandRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed class MxGatewaySession : IAsyncDisposable
|
||||
{
|
||||
public string SessionId { get; }
|
||||
|
||||
public Task<int> RegisterAsync(string clientName, CancellationToken ct = default);
|
||||
public Task UnregisterAsync(int serverHandle, CancellationToken ct = default);
|
||||
public Task<int> AddItemAsync(int serverHandle, string item, CancellationToken ct = default);
|
||||
public Task<int> AddItem2Async(int serverHandle, string item, string context, CancellationToken ct = default);
|
||||
public Task AdviseAsync(int serverHandle, int itemHandle, CancellationToken ct = default);
|
||||
public Task UnAdviseAsync(int serverHandle, int itemHandle, CancellationToken ct = default);
|
||||
public Task WriteAsync(int serverHandle, int itemHandle, MxValue value, int userId, CancellationToken ct = default);
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken ct = default);
|
||||
public Task CloseAsync(CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
Generated protobuf types should remain available under a generated namespace.
|
||||
Handwritten wrappers should not hide raw replies.
|
||||
|
||||
## Options
|
||||
|
||||
```csharp
|
||||
public sealed class MxGatewayClientOptions
|
||||
{
|
||||
public required Uri Endpoint { get; init; }
|
||||
public required string ApiKey { get; init; }
|
||||
public bool UseTls { get; init; }
|
||||
public string? CaCertificatePath { get; init; }
|
||||
public string? ServerNameOverride { get; init; }
|
||||
public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(10);
|
||||
public TimeSpan DefaultCallTimeout { get; init; } = TimeSpan.FromSeconds(30);
|
||||
public ILoggerFactory? LoggerFactory { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
API key may be loaded from `MXGATEWAY_API_KEY` by the CLI, not implicitly by the
|
||||
library constructor unless a helper explicitly says it does that.
|
||||
|
||||
## Auth Interceptor
|
||||
|
||||
Use a gRPC call credentials/interceptor layer to attach:
|
||||
|
||||
```text
|
||||
authorization: Bearer <api key>
|
||||
```
|
||||
|
||||
The interceptor must redact the key in logs and exceptions.
|
||||
|
||||
## Streaming
|
||||
|
||||
Expose `StreamEventsAsync` as `IAsyncEnumerable<MxEvent>`. On cancellation,
|
||||
cancel the gRPC stream and surface `OperationCanceledException` only when the
|
||||
caller initiated cancellation.
|
||||
|
||||
Do not reorder events.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Recommended exceptions:
|
||||
|
||||
```csharp
|
||||
MxGatewayException
|
||||
MxGatewayAuthenticationException
|
||||
MxGatewayAuthorizationException
|
||||
MxGatewaySessionException
|
||||
MxGatewayWorkerException
|
||||
MxGatewayCommandException
|
||||
MxAccessException
|
||||
```
|
||||
|
||||
For command replies that include MXAccess HRESULT/status, prefer returning the
|
||||
reply and exposing helper methods:
|
||||
|
||||
```csharp
|
||||
reply.EnsureProtocolSuccess();
|
||||
reply.EnsureMxAccessSuccess();
|
||||
```
|
||||
|
||||
## Test CLI
|
||||
|
||||
Project: `MxGateway.Client.Cli`.
|
||||
|
||||
Command examples:
|
||||
|
||||
```powershell
|
||||
mxgw-dotnet version
|
||||
mxgw-dotnet smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt
|
||||
mxgw-dotnet stream-events --session-id <id> --json
|
||||
mxgw-dotnet write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
|
||||
```
|
||||
|
||||
The CLI should use `System.CommandLine` or a similarly testable parser. JSON
|
||||
output should be deterministic and redact API keys.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Use an in-process fake gRPC service with `Grpc.AspNetCore.Server` test host or
|
||||
mock the generated client behind an internal interface.
|
||||
|
||||
Required tests:
|
||||
|
||||
- auth metadata is attached,
|
||||
- API key is redacted,
|
||||
- options build plaintext and TLS channels correctly,
|
||||
- `RegisterAsync` builds the right command payload,
|
||||
- `AddItem2Async` includes context,
|
||||
- `WriteAsync` converts scalar and array values,
|
||||
- command reply status helpers preserve MXAccess HRESULT,
|
||||
- `StreamEventsAsync` yields ordered events,
|
||||
- stream cancellation disposes the call,
|
||||
- CLI parsing and JSON output.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Use xUnit traits or categories. Skip unless:
|
||||
|
||||
```text
|
||||
MXGATEWAY_INTEGRATION=1
|
||||
MXGATEWAY_ENDPOINT=<endpoint>
|
||||
MXGATEWAY_API_KEY=<key>
|
||||
MXGATEWAY_TEST_ITEM=<item>
|
||||
```
|
||||
|
||||
Integration smoke should open, register, add, advise, stream for bounded time,
|
||||
and close.
|
||||
@@ -0,0 +1,172 @@
|
||||
# Go Client Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide an idiomatic Go client module for MXAccess Gateway, plus a test CLI and
|
||||
unit tests. The Go client should be suitable for services and command-line
|
||||
automation.
|
||||
|
||||
Follow the [Go Style Guide](./style-guides/GoStyleGuide.md) for handwritten
|
||||
code and the [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md) for
|
||||
generated contract inputs.
|
||||
|
||||
## Module Layout
|
||||
|
||||
Recommended layout:
|
||||
|
||||
```text
|
||||
clients/go/
|
||||
go.mod
|
||||
mxgateway/
|
||||
client.go
|
||||
session.go
|
||||
options.go
|
||||
auth.go
|
||||
values.go
|
||||
errors.go
|
||||
internal/generated/
|
||||
mxaccess_gateway.pb.go
|
||||
mxaccess_gateway_grpc.pb.go
|
||||
cmd/mxgw-go/
|
||||
main.go
|
||||
tests/
|
||||
```
|
||||
|
||||
Generated code should come from `protoc` plus:
|
||||
|
||||
- `protoc-gen-go`
|
||||
- `protoc-gen-go-grpc`
|
||||
|
||||
## Library API
|
||||
|
||||
Suggested API:
|
||||
|
||||
```go
|
||||
type Client struct {
|
||||
// owns grpc.ClientConn
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Endpoint string
|
||||
APIKey string
|
||||
Plaintext bool
|
||||
CACertFile string
|
||||
ServerNameOverride string
|
||||
DialTimeout time.Duration
|
||||
CallTimeout time.Duration
|
||||
}
|
||||
|
||||
func Dial(ctx context.Context, opts Options) (*Client, error)
|
||||
func (c *Client) OpenSession(ctx context.Context, opts OpenSessionOptions) (*Session, error)
|
||||
func (c *Client) Invoke(ctx context.Context, req *pb.MxCommandRequest) (*pb.MxCommandReply, error)
|
||||
func (c *Client) Close() error
|
||||
```
|
||||
|
||||
Session:
|
||||
|
||||
```go
|
||||
type Session struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (s *Session) Register(ctx context.Context, clientName string) (int32, error)
|
||||
func (s *Session) Unregister(ctx context.Context, serverHandle int32) error
|
||||
func (s *Session) AddItem(ctx context.Context, serverHandle int32, item string) (int32, error)
|
||||
func (s *Session) AddItem2(ctx context.Context, serverHandle int32, item, context string) (int32, error)
|
||||
func (s *Session) Advise(ctx context.Context, serverHandle, itemHandle int32) error
|
||||
func (s *Session) Write(ctx context.Context, serverHandle, itemHandle int32, value Value, userID int32) error
|
||||
func (s *Session) Events(ctx context.Context) (<-chan EventResult, error)
|
||||
func (s *Session) Close(ctx context.Context) error
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Use a unary and stream interceptor to attach:
|
||||
|
||||
```text
|
||||
authorization: Bearer <api key>
|
||||
```
|
||||
|
||||
The interceptor should use `metadata.AppendToOutgoingContext` or call options.
|
||||
Do not print API keys in errors.
|
||||
|
||||
## TLS
|
||||
|
||||
Support:
|
||||
|
||||
- `credentials/insecure` for local plaintext,
|
||||
- `credentials.NewClientTLSFromFile`,
|
||||
- custom `tls.Config` for advanced callers.
|
||||
|
||||
## Streaming
|
||||
|
||||
`Events(ctx)` should return a receive channel of:
|
||||
|
||||
```go
|
||||
type EventResult struct {
|
||||
Event *pb.MxEvent
|
||||
Err error
|
||||
}
|
||||
```
|
||||
|
||||
The receive goroutine exits on stream end, context cancellation, or error. The
|
||||
channel should be closed exactly once. Do not reorder events.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Expose typed errors:
|
||||
|
||||
```go
|
||||
type GatewayError struct { ... }
|
||||
type CommandError struct { ... }
|
||||
type MxAccessError struct { ... }
|
||||
```
|
||||
|
||||
Use `errors.Is` / `errors.As` support. Preserve raw protobuf replies on command
|
||||
errors.
|
||||
|
||||
## Test CLI
|
||||
|
||||
Binary: `mxgw-go`.
|
||||
|
||||
Recommended commands:
|
||||
|
||||
```text
|
||||
mxgw-go version
|
||||
mxgw-go smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt
|
||||
mxgw-go write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
|
||||
mxgw-go stream-events --session-id <id> --json
|
||||
```
|
||||
|
||||
Recommended CLI library:
|
||||
|
||||
- standard `flag` for minimalism, or
|
||||
- Cobra if subcommand ergonomics matter.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Use `bufconn` for in-memory gRPC tests.
|
||||
|
||||
Required tests:
|
||||
|
||||
- auth interceptor on unary calls,
|
||||
- auth interceptor on streaming calls,
|
||||
- plaintext and TLS dial options,
|
||||
- command helper request construction,
|
||||
- value conversion,
|
||||
- status conversion,
|
||||
- typed error wrapping,
|
||||
- stream channel closes on cancellation,
|
||||
- late stream error propagation,
|
||||
- CLI JSON redaction.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Use Go build tags or environment skip:
|
||||
|
||||
```text
|
||||
MXGATEWAY_INTEGRATION=1
|
||||
```
|
||||
|
||||
Integration test should run `OpenSession`, `Register`, `AddItem`, `Advise`,
|
||||
bounded `StreamEvents`, and `CloseSession`.
|
||||
@@ -0,0 +1,191 @@
|
||||
# Java Client Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide a Java client library for MXAccess Gateway, plus a test CLI and unit
|
||||
tests. The Java client should work for JVM services and operator tooling.
|
||||
|
||||
Follow the [Java Style Guide](./style-guides/JavaStyleGuide.md) for handwritten
|
||||
code and the [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md) for
|
||||
generated contract inputs.
|
||||
|
||||
## Build Layout
|
||||
|
||||
Recommended Gradle multi-project layout:
|
||||
|
||||
```text
|
||||
clients/java/
|
||||
settings.gradle
|
||||
build.gradle
|
||||
mxgateway-client/
|
||||
build.gradle
|
||||
src/main/java/com/dohertylan/mxgateway/client/
|
||||
src/test/java/com/dohertylan/mxgateway/client/
|
||||
mxgateway-cli/
|
||||
build.gradle
|
||||
src/main/java/com/dohertylan/mxgateway/cli/
|
||||
```
|
||||
|
||||
Alternative Maven layout is acceptable if the repo standardizes on Maven.
|
||||
|
||||
Target Java:
|
||||
|
||||
- Java 21 recommended.
|
||||
|
||||
Expected dependencies:
|
||||
|
||||
- `grpc-netty-shaded`
|
||||
- `grpc-protobuf`
|
||||
- `grpc-stub`
|
||||
- `protobuf-java`
|
||||
- `picocli`
|
||||
- `junit-jupiter`
|
||||
- `mockito` if needed
|
||||
|
||||
## Library API
|
||||
|
||||
Suggested API:
|
||||
|
||||
```java
|
||||
public final class MxGatewayClient implements AutoCloseable {
|
||||
public static MxGatewayClient connect(MxGatewayClientOptions options);
|
||||
public MxGatewaySession openSession(OpenSessionOptions options);
|
||||
public MxCommandReply invoke(MxCommandRequest request);
|
||||
public CompletableFuture<MxCommandReply> invokeAsync(MxCommandRequest request);
|
||||
public void close();
|
||||
}
|
||||
|
||||
public final class MxGatewaySession implements AutoCloseable {
|
||||
public String sessionId();
|
||||
public int register(String clientName);
|
||||
public void unregister(int serverHandle);
|
||||
public int addItem(int serverHandle, String item);
|
||||
public int addItem2(int serverHandle, String item, String context);
|
||||
public void advise(int serverHandle, int itemHandle);
|
||||
public void write(int serverHandle, int itemHandle, MxValue value, int userId);
|
||||
public Iterator<MxEvent> streamEvents();
|
||||
public void streamEventsAsync(StreamObserver<MxEvent> observer);
|
||||
public void close();
|
||||
}
|
||||
```
|
||||
|
||||
Expose generated protobuf classes for callers that need raw access.
|
||||
|
||||
## Options
|
||||
|
||||
```java
|
||||
public final class MxGatewayClientOptions {
|
||||
URI endpoint;
|
||||
String apiKey;
|
||||
boolean plaintext;
|
||||
Path caCertificatePath;
|
||||
String serverNameOverride;
|
||||
Duration connectTimeout;
|
||||
Duration callTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Use a gRPC `ClientInterceptor` to attach:
|
||||
|
||||
```text
|
||||
authorization: Bearer <api key>
|
||||
```
|
||||
|
||||
Redact API keys in `toString`, logs, and CLI output.
|
||||
|
||||
## TLS
|
||||
|
||||
Support:
|
||||
|
||||
- plaintext for local development,
|
||||
- TLS with default JVM trust store,
|
||||
- custom CA certificate file,
|
||||
- server name override for test environments.
|
||||
|
||||
## Streaming
|
||||
|
||||
Support both:
|
||||
|
||||
- blocking iterator for simple CLIs,
|
||||
- async `StreamObserver` for services.
|
||||
|
||||
Do not reorder events. Stream cancellation should call `ClientCall.cancel`.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Recommended exceptions:
|
||||
|
||||
```java
|
||||
MxGatewayException
|
||||
MxGatewayAuthenticationException
|
||||
MxGatewayAuthorizationException
|
||||
MxGatewaySessionException
|
||||
MxGatewayWorkerException
|
||||
MxGatewayCommandException
|
||||
MxAccessException
|
||||
```
|
||||
|
||||
`MxGatewayCommandException` should carry the raw command reply when available.
|
||||
|
||||
## Test CLI
|
||||
|
||||
Binary wrapper name:
|
||||
|
||||
```text
|
||||
mxgw-java
|
||||
```
|
||||
|
||||
Use `picocli`.
|
||||
|
||||
Commands:
|
||||
|
||||
```text
|
||||
mxgw-java version
|
||||
mxgw-java smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt
|
||||
mxgw-java stream-events --session-id <id> --json
|
||||
mxgw-java write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
|
||||
```
|
||||
|
||||
JSON output can use Jackson or protobuf JSON formatting. Keep it deterministic.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Use JUnit 5.
|
||||
|
||||
Use `InProcessServerBuilder` and `InProcessChannelBuilder` for fake gRPC tests.
|
||||
|
||||
Required tests:
|
||||
|
||||
- auth interceptor attaches metadata,
|
||||
- key redaction,
|
||||
- plaintext and TLS channel setup,
|
||||
- request construction helpers,
|
||||
- value conversion,
|
||||
- status/error mapping,
|
||||
- blocking event stream iteration,
|
||||
- async stream observer cancellation,
|
||||
- CLI parsing,
|
||||
- JSON output.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Skip unless:
|
||||
|
||||
```text
|
||||
MXGATEWAY_INTEGRATION=1
|
||||
```
|
||||
|
||||
Use JUnit assumptions. Integration flow should open, register, add, advise,
|
||||
stream for bounded time, and close.
|
||||
|
||||
## Packaging
|
||||
|
||||
Publish library and CLI separately:
|
||||
|
||||
- `mxgateway-client` jar,
|
||||
- `mxgateway-cli` runnable distribution.
|
||||
|
||||
Generated protobuf code should be produced during the build from shared proto
|
||||
files and should not be hand-edited.
|
||||
@@ -0,0 +1,191 @@
|
||||
# Python Client Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide an async Python client package for MXAccess Gateway, plus a test CLI and
|
||||
unit tests. The Python client should be useful for automation, diagnostics, and
|
||||
test harnesses.
|
||||
|
||||
Follow the [Python Style Guide](./style-guides/PythonStyleGuide.md) for
|
||||
handwritten code and the [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
|
||||
for generated contract inputs.
|
||||
|
||||
## Package Layout
|
||||
|
||||
Recommended layout:
|
||||
|
||||
```text
|
||||
clients/python/
|
||||
pyproject.toml
|
||||
src/mxgateway/
|
||||
__init__.py
|
||||
client.py
|
||||
session.py
|
||||
options.py
|
||||
auth.py
|
||||
values.py
|
||||
errors.py
|
||||
generated/
|
||||
src/mxgateway_cli/
|
||||
__main__.py
|
||||
commands.py
|
||||
tests/
|
||||
```
|
||||
|
||||
Expected dependencies:
|
||||
|
||||
- `grpcio`
|
||||
- `grpcio-tools`
|
||||
- `protobuf`
|
||||
- `click` or `typer`
|
||||
- `pytest`
|
||||
- `pytest-asyncio`
|
||||
|
||||
## Library API
|
||||
|
||||
Use async-first API. A sync wrapper can be added later if needed.
|
||||
|
||||
Suggested API:
|
||||
|
||||
```python
|
||||
client = await GatewayClient.connect(
|
||||
endpoint="localhost:5000",
|
||||
api_key=api_key,
|
||||
plaintext=True,
|
||||
)
|
||||
|
||||
session = await client.open_session()
|
||||
server = await session.register("python-client")
|
||||
item = await session.add_item(server, "TestChildObject.TestInt")
|
||||
await session.advise(server, item)
|
||||
|
||||
async for event in session.stream_events():
|
||||
...
|
||||
|
||||
await session.close()
|
||||
await client.close()
|
||||
```
|
||||
|
||||
Classes:
|
||||
|
||||
```python
|
||||
class GatewayClient:
|
||||
@classmethod
|
||||
async def connect(cls, options: ClientOptions) -> "GatewayClient": ...
|
||||
async def open_session(self, options: OpenSessionOptions | None = None) -> "Session": ...
|
||||
async def invoke(self, request: MxCommandRequest) -> MxCommandReply: ...
|
||||
async def close(self) -> None: ...
|
||||
|
||||
class Session:
|
||||
session_id: str
|
||||
async def register(self, client_name: str) -> int: ...
|
||||
async def add_item(self, server_handle: int, item: str) -> int: ...
|
||||
async def add_item2(self, server_handle: int, item: str, context: str) -> int: ...
|
||||
async def advise(self, server_handle: int, item_handle: int) -> None: ...
|
||||
async def write(self, server_handle: int, item_handle: int, value: MxValueInput, user_id: int = 0) -> None: ...
|
||||
async def stream_events(self) -> AsyncIterator[MxEvent]: ...
|
||||
async def close(self) -> None: ...
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Use gRPC metadata:
|
||||
|
||||
```python
|
||||
metadata = (("authorization", f"Bearer {api_key}"),)
|
||||
```
|
||||
|
||||
Provide a metadata helper that all unary and streaming calls use. Redact API
|
||||
keys in exceptions and CLI output.
|
||||
|
||||
## TLS
|
||||
|
||||
Support:
|
||||
|
||||
- insecure channel for local development,
|
||||
- TLS channel with default roots,
|
||||
- custom root certificate file.
|
||||
|
||||
## Streaming
|
||||
|
||||
Expose `stream_events` as an async iterator. Canceling the task should cancel
|
||||
the gRPC stream.
|
||||
|
||||
Do not hide stream errors. Convert common auth/session errors into typed
|
||||
exceptions.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Define typed exceptions:
|
||||
|
||||
```python
|
||||
MxGatewayError
|
||||
MxGatewayTransportError
|
||||
MxGatewayAuthenticationError
|
||||
MxGatewayAuthorizationError
|
||||
MxGatewaySessionError
|
||||
MxGatewayWorkerError
|
||||
MxGatewayCommandError
|
||||
MxAccessError
|
||||
```
|
||||
|
||||
`MxGatewayCommandError` should include the raw protobuf reply when available.
|
||||
|
||||
## Test CLI
|
||||
|
||||
Entry point:
|
||||
|
||||
```text
|
||||
mxgw-py
|
||||
```
|
||||
|
||||
Recommended commands:
|
||||
|
||||
```text
|
||||
mxgw-py version
|
||||
mxgw-py smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestChildObject.TestInt
|
||||
mxgw-py stream-events --session-id <id> --json
|
||||
mxgw-py write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
|
||||
```
|
||||
|
||||
Use `click` or `typer`. JSON output should be stable for test automation.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Use `pytest` and `pytest-asyncio`.
|
||||
|
||||
Use fake generated stubs or an in-process test gRPC server where practical.
|
||||
|
||||
Required tests:
|
||||
|
||||
- API key metadata injection,
|
||||
- API key redaction,
|
||||
- insecure and TLS channel option construction,
|
||||
- request construction for method helpers,
|
||||
- value conversion from Python values,
|
||||
- status/error mapping,
|
||||
- async event iteration,
|
||||
- stream cancellation,
|
||||
- CLI parsing,
|
||||
- JSON output.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Skip unless:
|
||||
|
||||
```text
|
||||
MXGATEWAY_INTEGRATION=1
|
||||
```
|
||||
|
||||
Use bounded smoke flow and always attempt `close_session` in `finally`.
|
||||
|
||||
## Packaging
|
||||
|
||||
Use `pyproject.toml`. Publishable package name should be stable, for example:
|
||||
|
||||
```text
|
||||
mxaccess-gateway-client
|
||||
```
|
||||
|
||||
Generated protobuf code should be regenerated through a documented command, not
|
||||
edited by hand.
|
||||
@@ -0,0 +1,183 @@
|
||||
# Rust Client Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide an async Rust client crate for MXAccess Gateway, plus a test CLI and
|
||||
unit tests. The Rust client should use `tonic` and `tokio`.
|
||||
|
||||
Follow the [Rust Style Guide](./style-guides/RustStyleGuide.md) for handwritten
|
||||
code and the [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md) for
|
||||
generated contract inputs.
|
||||
|
||||
## Crate Layout
|
||||
|
||||
Recommended layout:
|
||||
|
||||
```text
|
||||
clients/rust/
|
||||
Cargo.toml
|
||||
build.rs
|
||||
crates/
|
||||
mxgateway-client/
|
||||
src/lib.rs
|
||||
src/client.rs
|
||||
src/session.rs
|
||||
src/options.rs
|
||||
src/auth.rs
|
||||
src/value.rs
|
||||
src/error.rs
|
||||
src/generated/
|
||||
mxgw-cli/
|
||||
src/main.rs
|
||||
tests/
|
||||
```
|
||||
|
||||
Expected dependencies:
|
||||
|
||||
- `tonic`
|
||||
- `prost`
|
||||
- `prost-types`
|
||||
- `tokio`
|
||||
- `tokio-stream`
|
||||
- `thiserror`
|
||||
- `clap`
|
||||
- `serde`
|
||||
- `serde_json`
|
||||
- `tracing`
|
||||
|
||||
## Library API
|
||||
|
||||
Suggested API:
|
||||
|
||||
```rust
|
||||
pub struct GatewayClient { /* tonic channel + generated client */ }
|
||||
|
||||
pub struct ClientOptions {
|
||||
pub endpoint: String,
|
||||
pub api_key: String,
|
||||
pub plaintext: bool,
|
||||
pub ca_file: Option<PathBuf>,
|
||||
pub server_name_override: Option<String>,
|
||||
pub connect_timeout: Duration,
|
||||
pub call_timeout: Duration,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
pub async fn connect(options: ClientOptions) -> Result<Self, Error>;
|
||||
pub async fn open_session(&self, options: OpenSessionOptions) -> Result<Session, Error>;
|
||||
pub async fn invoke(&self, request: MxCommandRequest) -> Result<MxCommandReply, Error>;
|
||||
}
|
||||
```
|
||||
|
||||
Session:
|
||||
|
||||
```rust
|
||||
pub struct Session {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub async fn register(&self, client_name: &str) -> Result<i32, Error>;
|
||||
pub async fn add_item(&self, server_handle: i32, item: &str) -> Result<i32, Error>;
|
||||
pub async fn add_item2(&self, server_handle: i32, item: &str, context: &str) -> Result<i32, Error>;
|
||||
pub async fn advise(&self, server_handle: i32, item_handle: i32) -> Result<(), Error>;
|
||||
pub async fn write(&self, server_handle: i32, item_handle: i32, value: MxValue, user_id: i32) -> Result<(), Error>;
|
||||
pub async fn events(&self) -> Result<impl Stream<Item = Result<MxEvent, Error>>, Error>;
|
||||
pub async fn close(&self) -> Result<(), Error>;
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Use a `tonic` interceptor or request extension layer to add:
|
||||
|
||||
```text
|
||||
authorization: Bearer <api key>
|
||||
```
|
||||
|
||||
Use `SecretString` or equivalent if a dependency is acceptable. Always redact
|
||||
API keys in `Debug` output.
|
||||
|
||||
## TLS
|
||||
|
||||
Support:
|
||||
|
||||
- plaintext channel for local development,
|
||||
- native or rustls TLS depending on project preference,
|
||||
- custom CA file,
|
||||
- domain override.
|
||||
|
||||
## Streaming
|
||||
|
||||
Expose event streams as a `Stream<Item = Result<MxEvent, Error>>`. Dropping the
|
||||
stream should cancel the underlying gRPC stream.
|
||||
|
||||
Do not buffer unboundedly in the client. If a helper channel is used, make it
|
||||
bounded.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Use `thiserror`:
|
||||
|
||||
```rust
|
||||
pub enum Error {
|
||||
Transport(tonic::transport::Error),
|
||||
Status(tonic::Status),
|
||||
Authentication(String),
|
||||
Authorization(String),
|
||||
Session(SessionError),
|
||||
Worker(WorkerError),
|
||||
Command(CommandError),
|
||||
MxAccess(MxAccessError),
|
||||
Timeout,
|
||||
Cancelled,
|
||||
}
|
||||
```
|
||||
|
||||
Preserve raw command replies in `CommandError` where applicable.
|
||||
|
||||
## Test CLI
|
||||
|
||||
Binary: `mxgw`.
|
||||
|
||||
Use `clap` derive.
|
||||
|
||||
Commands:
|
||||
|
||||
```text
|
||||
mxgw version
|
||||
mxgw smoke --endpoint http://localhost:5000 --api-key-env MXGATEWAY_API_KEY --item TestChildObject.TestInt
|
||||
mxgw stream-events --session-id <id> --json
|
||||
mxgw write --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123
|
||||
```
|
||||
|
||||
JSON output should use `serde_json`.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Use a fake `tonic` server started on a local ephemeral port, or abstract the
|
||||
generated client behind a trait for unit tests.
|
||||
|
||||
Required tests:
|
||||
|
||||
- generated client compiles from proto,
|
||||
- auth metadata injection,
|
||||
- TLS/plaintext endpoint construction,
|
||||
- value conversion,
|
||||
- command request construction,
|
||||
- error mapping from `tonic::Status`,
|
||||
- event stream order,
|
||||
- stream cancellation,
|
||||
- CLI parsing,
|
||||
- JSON redaction.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Skip unless:
|
||||
|
||||
```text
|
||||
MXGATEWAY_INTEGRATION=1
|
||||
```
|
||||
|
||||
Use `tokio::test`. Run bounded smoke flow and ensure `CloseSession` is attempted
|
||||
with `drop` fallback docs, but do not rely on `Drop` for async close.
|
||||
@@ -0,0 +1,309 @@
|
||||
# Design Decisions
|
||||
|
||||
This document records current v1 choices for the MXAccess gateway design. These
|
||||
decisions can change, but implementation should follow them until a later design
|
||||
update says otherwise.
|
||||
|
||||
## Source References
|
||||
|
||||
Use these local analysis sources when answering MXAccess-specific design or
|
||||
implementation questions:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\mxaccess
|
||||
C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Public-API.md
|
||||
C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Reverse-Engineering.md
|
||||
```
|
||||
|
||||
Use these local notes for Galaxy Repository SQL metadata:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\lmxopcua\gr
|
||||
```
|
||||
|
||||
## MXAccess COM Target
|
||||
|
||||
Decision: target the installed MXAccess COM interop surface directly from the
|
||||
x86 worker.
|
||||
|
||||
Concrete COM details from the MXAccess analysis:
|
||||
|
||||
- Interop assembly:
|
||||
`C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`
|
||||
- Assembly identity:
|
||||
`ArchestrA.MxAccess, Version=3.2.0.0, PublicKeyToken=23106a86e706d0ae`
|
||||
- COM class:
|
||||
`ArchestrA.MxAccess.LMXProxyServerClass`
|
||||
- CLSID:
|
||||
`{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
||||
- ProgID:
|
||||
`LMXProxy.LMXProxyServer.1`
|
||||
- Version-independent ProgID:
|
||||
`LMXProxy.LMXProxyServer`
|
||||
- Registered server:
|
||||
`C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll`
|
||||
- Registry view:
|
||||
`HKCR\Wow6432Node\CLSID\{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
||||
- Threading model:
|
||||
`Apartment`
|
||||
|
||||
Rationale: `LMXProxyServer` is a 32-bit in-process COM server, so a .NET 10 x64
|
||||
gateway cannot instantiate it directly. The x86 sidecar worker is the reliable
|
||||
parity path.
|
||||
|
||||
Implementation guidance:
|
||||
|
||||
- Worker should reference `ArchestrA.MXAccess.dll`.
|
||||
- Worker should instantiate `new LMXProxyServerClass()` on the dedicated STA.
|
||||
- Worker should expose the resolved class, ProgID, CLSID, interop assembly
|
||||
version, and `LmxProxy.dll` path through `GetWorkerInfo` / `WorkerReady`.
|
||||
- Keep the ProgID/path configurable for diagnostics, but the default should be
|
||||
the installed MXAccess class above.
|
||||
|
||||
## Session Reconnect
|
||||
|
||||
Decision: no reconnectable sessions for v1.
|
||||
|
||||
One `OpenSession` creates one gateway session and one worker process. The
|
||||
session ends on `CloseSession`, client disconnect policy, lease expiry, worker
|
||||
fault, or gateway shutdown.
|
||||
|
||||
Rationale: reconnectable sessions require event replay, orphan ownership,
|
||||
security checks, and more complicated worker lifetime rules. They are not needed
|
||||
for the first parity slice.
|
||||
|
||||
## Event Subscribers
|
||||
|
||||
Decision: one active `StreamEvents` subscriber per session for v1.
|
||||
|
||||
A second subscriber should be rejected with a clear session error. Multi-client
|
||||
fan-out may be added later with explicit backpressure semantics.
|
||||
|
||||
Rationale: one subscriber preserves simple event ordering and failure behavior
|
||||
while parity is being proven.
|
||||
|
||||
## Authentication
|
||||
|
||||
Decision: API key authentication for the public gateway.
|
||||
|
||||
API keys are stored in a gateway-owned SQLite database. Store hashed API key
|
||||
secrets only; never store raw key material.
|
||||
|
||||
Recommended client format:
|
||||
|
||||
```text
|
||||
authorization: Bearer mxgw_<key-id>_<secret>
|
||||
```
|
||||
|
||||
Recommended SQLite tables:
|
||||
|
||||
```sql
|
||||
CREATE TABLE api_keys (
|
||||
key_id TEXT PRIMARY KEY,
|
||||
key_prefix TEXT NOT NULL,
|
||||
secret_hash BLOB NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
scopes TEXT NOT NULL,
|
||||
created_utc TEXT NOT NULL,
|
||||
last_used_utc TEXT NULL,
|
||||
revoked_utc TEXT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE api_key_audit (
|
||||
audit_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
key_id TEXT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
remote_address TEXT NULL,
|
||||
created_utc TEXT NOT NULL,
|
||||
details TEXT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Recommended scopes:
|
||||
|
||||
- `session:open`
|
||||
- `session:close`
|
||||
- `invoke:read`
|
||||
- `invoke:write`
|
||||
- `invoke:secure`
|
||||
- `events:read`
|
||||
- `metadata:read`
|
||||
- `admin`
|
||||
|
||||
Hashing recommendation:
|
||||
|
||||
- Use HMAC-SHA256 with a gateway-local secret/pepper stored outside SQLite, or
|
||||
use Argon2id if a suitable dependency is already accepted.
|
||||
- Compare hashes using constant-time comparison.
|
||||
- Log only the key id or prefix, not the raw key.
|
||||
|
||||
Storage recommendation:
|
||||
|
||||
- Default SQLite path should be under `ProgramData` or another configured
|
||||
gateway data directory.
|
||||
- Apply restrictive filesystem ACLs for the gateway service identity and
|
||||
administrators.
|
||||
- Require TLS when the gateway is reachable off-machine.
|
||||
|
||||
## Authorization
|
||||
|
||||
Decision: start with scope checks by command category.
|
||||
|
||||
Suggested mapping:
|
||||
|
||||
- `OpenSession`: `session:open`
|
||||
- `CloseSession`: `session:close`
|
||||
- `Register`, `Unregister`, `AddItem`, `AddItem2`, `RemoveItem`, `Advise`,
|
||||
`UnAdvise`, `AdviseSupervisory`, `AddBufferedItem`,
|
||||
`SetBufferedUpdateInterval`, `Suspend`, `Activate`: `invoke:read`
|
||||
- `Write`, `Write2`: `invoke:write`
|
||||
- `WriteSecured`, `WriteSecured2`, `AuthenticateUser`,
|
||||
`ArchestrAUserToId`: `invoke:secure`
|
||||
- `StreamEvents`: `events:read`
|
||||
- Galaxy SQL metadata endpoints if added: `metadata:read`
|
||||
- worker shutdown diagnostics and key management: `admin`
|
||||
|
||||
## Worker Process Identity
|
||||
|
||||
Decision: run workers as the gateway service identity for v1.
|
||||
|
||||
Rationale: this avoids early COM/DCOM permission failures and keeps the first
|
||||
implementation focused on MXAccess parity. The worker launcher should keep an
|
||||
extension point for a restricted service account later.
|
||||
|
||||
## Event Backpressure
|
||||
|
||||
Decision: fail-fast bounded queues for v1 and parity testing.
|
||||
|
||||
If worker or gateway event queues fill, fault the session. Do not silently drop
|
||||
or coalesce events in parity mode.
|
||||
|
||||
Rationale: event drops would hide parity defects. Production coalescing by item
|
||||
handle can be added later as an explicit opt-in mode once event rates are
|
||||
measured.
|
||||
|
||||
## Event-Rate Target
|
||||
|
||||
Decision: do not set a production event-rate target before measurement.
|
||||
|
||||
For v1, expose queue depth, event rate, stream send latency, and overflow
|
||||
metrics. Keep bounded queues and fail-fast behavior. Use observed load from live
|
||||
systems to set a later coalescing or scaling target.
|
||||
|
||||
## Command Batching
|
||||
|
||||
Decision: no public command batching for v1.
|
||||
|
||||
Use one command per request so replies, HRESULTs, status arrays, event ordering,
|
||||
and failure behavior are easy to compare against direct MXAccess.
|
||||
|
||||
Batch tag registration can be added later if measured setup latency requires it.
|
||||
|
||||
## Graceful Worker Shutdown
|
||||
|
||||
Decision: best-effort cleanup before COM release.
|
||||
|
||||
During graceful shutdown, the worker should attempt:
|
||||
|
||||
1. `UnAdvise` for advised items.
|
||||
2. `RemoveItem` for active item handles.
|
||||
3. `Unregister` for active server handles.
|
||||
4. Event detach.
|
||||
5. COM release.
|
||||
|
||||
Failures during cleanup should be logged and preserved diagnostically, but the
|
||||
gateway may still kill the worker after shutdown timeout.
|
||||
|
||||
## OperationComplete
|
||||
|
||||
Decision: model and forward `OperationComplete` only when native MXAccess fires
|
||||
it. Do not synthesize `OperationComplete` from writes, command replies, ASB
|
||||
completion queues, or other status frames.
|
||||
|
||||
Rationale: the event signature is known, but the MXAccess analysis has not yet
|
||||
captured the runtime condition that triggers the public event. Synthesizing it
|
||||
would risk breaking parity.
|
||||
|
||||
## Buffered Data Change
|
||||
|
||||
Decision: include `OnBufferedDataChange` in the protocol and worker event
|
||||
model, but treat multi-sample payload conversion as capture-validated work.
|
||||
|
||||
The event signature and native path are known. A live buffered sample batch has
|
||||
not yet been observed. Until then, preserve raw value, quality, timestamp, data
|
||||
type, and status metadata whenever conversion is incomplete.
|
||||
|
||||
## Completion-Only Status Mapping
|
||||
|
||||
Decision: preserve completion-only operation-status bytes as raw diagnostic
|
||||
metadata unless native MXAccess raises a public event or the MXAccess analysis
|
||||
proves an exact `MXSTATUS_PROXY[]` mapping.
|
||||
|
||||
Do not guess status category/source/detail values for frames that MXAccess does
|
||||
not expose through its public COM events.
|
||||
|
||||
## API Key Administration
|
||||
|
||||
Decision: v1 API key management is a local administrative CLI/tool, not a
|
||||
public admin API.
|
||||
|
||||
The tool should support:
|
||||
|
||||
- initialize auth database,
|
||||
- create key,
|
||||
- list keys without showing secrets,
|
||||
- revoke key,
|
||||
- rotate key,
|
||||
- print the raw secret exactly once at creation.
|
||||
|
||||
Public gRPC key-management endpoints can be added later only behind `admin`
|
||||
scope and TLS.
|
||||
|
||||
## SQLite Migrations
|
||||
|
||||
Decision: use simple startup migrations with a `schema_version` table.
|
||||
|
||||
Recommended table:
|
||||
|
||||
```sql
|
||||
CREATE TABLE schema_version (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
version INTEGER NOT NULL,
|
||||
applied_utc TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Migrations should be idempotent, run inside transactions, and fail gateway
|
||||
startup if the database is newer than the running binary understands.
|
||||
|
||||
## Web Dashboard
|
||||
|
||||
Decision: host a basic gateway dashboard with Blazor Server and Bootstrap
|
||||
CSS/JS.
|
||||
|
||||
The dashboard should show gateway health, active sessions, worker instances,
|
||||
basic metrics, queue depths, and recent faults. It should update in real time
|
||||
through Blazor Server component updates.
|
||||
|
||||
Allowed UI stack:
|
||||
|
||||
- Blazor Server,
|
||||
- Bootstrap CSS,
|
||||
- Bootstrap JavaScript,
|
||||
- small local CSS.
|
||||
|
||||
Do not use MudBlazor or other Blazor UI component libraries for v1.
|
||||
|
||||
Dashboard access should require API-key-backed dashboard authentication with
|
||||
`admin` scope when enabled. For local development, anonymous localhost access
|
||||
may exist only behind an explicit configuration option that defaults to false.
|
||||
|
||||
## Later Revisit Items
|
||||
|
||||
These are explicit post-v1 revisit items, not open blockers:
|
||||
|
||||
- reconnectable sessions,
|
||||
- multiple event subscribers per session,
|
||||
- restricted worker service account,
|
||||
- production coalescing by item handle,
|
||||
- command batching for high-volume tag setup.
|
||||
@@ -0,0 +1,364 @@
|
||||
# Gateway Dashboard Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
The gateway should host a basic web dashboard for operators and developers. The
|
||||
dashboard is diagnostic and operational visibility only for v1. It should show
|
||||
gateway health, active MXAccess worker instances, session state, and basic
|
||||
statistics in real time.
|
||||
|
||||
## Technology Choice
|
||||
|
||||
Decision: Blazor Server with Bootstrap CSS/JS.
|
||||
|
||||
Allowed UI stack:
|
||||
|
||||
- ASP.NET Core Blazor Server,
|
||||
- Bootstrap CSS,
|
||||
- Bootstrap JavaScript,
|
||||
- small local CSS for layout and status styling,
|
||||
- built-in Blazor components.
|
||||
|
||||
Not allowed for v1:
|
||||
|
||||
- MudBlazor,
|
||||
- Radzen,
|
||||
- Syncfusion,
|
||||
- Telerik,
|
||||
- other Blazor UI component libraries,
|
||||
- client-side SPA framework replacement.
|
||||
|
||||
Rationale: Blazor Server keeps the dashboard in the gateway process, avoids a
|
||||
separate frontend build, and gives real-time UI updates through the Blazor
|
||||
SignalR circuit. Bootstrap is sufficient for a basic dashboard.
|
||||
|
||||
## Hosting Model
|
||||
|
||||
The dashboard is hosted by `MxGateway.Server` alongside the gRPC API.
|
||||
|
||||
Suggested endpoint layout:
|
||||
|
||||
```text
|
||||
/dashboard
|
||||
/dashboard/sessions
|
||||
/dashboard/sessions/{sessionId}
|
||||
/dashboard/workers
|
||||
/dashboard/events
|
||||
/dashboard/settings
|
||||
/_blazor
|
||||
```
|
||||
|
||||
The app should redirect `/` to `/dashboard` only if the deployment wants the
|
||||
dashboard as the default web page. Otherwise leave gRPC/API hosting unaffected.
|
||||
|
||||
## High-Level Components
|
||||
|
||||
```text
|
||||
MxGateway.Server
|
||||
Dashboard/
|
||||
Components/
|
||||
App.razor
|
||||
Routes.razor
|
||||
Layout/
|
||||
DashboardLayout.razor
|
||||
NavMenu.razor
|
||||
Pages/
|
||||
DashboardHome.razor
|
||||
SessionsPage.razor
|
||||
SessionDetailsPage.razor
|
||||
WorkersPage.razor
|
||||
EventsPage.razor
|
||||
SettingsPage.razor
|
||||
Components/
|
||||
MetricCard.razor
|
||||
SessionTable.razor
|
||||
WorkerTable.razor
|
||||
EventRatePanel.razor
|
||||
FaultList.razor
|
||||
Services/
|
||||
DashboardSnapshotService.cs
|
||||
DashboardUpdateHub.cs
|
||||
DashboardAuthorization.cs
|
||||
Models/
|
||||
DashboardSnapshot.cs
|
||||
SessionSummary.cs
|
||||
WorkerSummary.cs
|
||||
MetricSummary.cs
|
||||
```
|
||||
|
||||
`DashboardUpdateHub` here means an internal application update service, not a
|
||||
separate public SignalR hub unless implementation proves one is needed. Blazor
|
||||
Server already uses SignalR for UI circuits.
|
||||
|
||||
## Dashboard Data Source
|
||||
|
||||
The dashboard should consume read-only snapshots from gateway services:
|
||||
|
||||
- `SessionRegistry`,
|
||||
- `SessionManager`,
|
||||
- `WorkerClient`,
|
||||
- `GatewayMetrics`,
|
||||
- health checks,
|
||||
- structured fault/event counters.
|
||||
|
||||
Do not let Razor components directly mutate gateway session or worker objects.
|
||||
Create a small read-only dashboard service that projects gateway state into
|
||||
plain DTOs.
|
||||
|
||||
Suggested service:
|
||||
|
||||
```csharp
|
||||
public interface IDashboardSnapshotService
|
||||
{
|
||||
DashboardSnapshot GetSnapshot();
|
||||
IAsyncEnumerable<DashboardSnapshot> WatchSnapshotsAsync(
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
Snapshot updates can be driven by:
|
||||
|
||||
- periodic timer, default every 1 second,
|
||||
- session lifecycle notifications,
|
||||
- worker heartbeat updates,
|
||||
- event counter updates,
|
||||
- fault notifications.
|
||||
|
||||
Use immutable snapshot DTOs so Razor components can render without locking
|
||||
gateway internals.
|
||||
|
||||
## Realtime Updates
|
||||
|
||||
Use Blazor Server component state updates for real-time dashboard refresh.
|
||||
|
||||
Recommended pattern:
|
||||
|
||||
1. Page/component subscribes to `WatchSnapshotsAsync`.
|
||||
2. Snapshot service emits updates from a bounded channel or timer.
|
||||
3. Component stores the latest snapshot.
|
||||
4. Component calls `InvokeAsync(StateHasChanged)`.
|
||||
5. Component cancels subscription on dispose.
|
||||
|
||||
Default update cadence:
|
||||
|
||||
- immediate update on session create/close/fault,
|
||||
- immediate update on worker fault,
|
||||
- periodic metrics refresh every 1 second,
|
||||
- event-rate windows updated every 1 second.
|
||||
|
||||
Avoid pushing every MXAccess data-change event to the dashboard. Aggregate event
|
||||
counts and rates instead.
|
||||
|
||||
## Pages
|
||||
|
||||
### Dashboard Home
|
||||
|
||||
Show top-level status:
|
||||
|
||||
- gateway status,
|
||||
- gateway version,
|
||||
- uptime,
|
||||
- open sessions,
|
||||
- workers running,
|
||||
- sessions faulted,
|
||||
- command rate,
|
||||
- command failure count,
|
||||
- event rate,
|
||||
- event queue depth,
|
||||
- worker restart/kill count.
|
||||
|
||||
Use Bootstrap cards for individual metric summaries. Keep the layout compact
|
||||
and operational.
|
||||
|
||||
### Sessions Page
|
||||
|
||||
Show active and recent sessions in a table:
|
||||
|
||||
- session id,
|
||||
- client identity or API key display name,
|
||||
- state,
|
||||
- backend,
|
||||
- worker process id,
|
||||
- open time,
|
||||
- last client activity,
|
||||
- last worker heartbeat,
|
||||
- active event subscribers,
|
||||
- pending commands,
|
||||
- event queue depth,
|
||||
- last fault summary.
|
||||
|
||||
Rows should link to session details.
|
||||
|
||||
### Session Details Page
|
||||
|
||||
Show:
|
||||
|
||||
- session metadata,
|
||||
- worker metadata,
|
||||
- command counters by method,
|
||||
- event counters by family,
|
||||
- active server handles and item counts if gateway shadow state has them,
|
||||
- latest faults,
|
||||
- last heartbeat payload,
|
||||
- close/kill controls only if admin actions are later enabled.
|
||||
|
||||
For v1, details should be read-only unless an explicit admin action design is
|
||||
added.
|
||||
|
||||
### Workers Page
|
||||
|
||||
Show:
|
||||
|
||||
- worker process id,
|
||||
- session id,
|
||||
- executable path/version,
|
||||
- state,
|
||||
- startup duration,
|
||||
- memory and CPU if available,
|
||||
- last heartbeat,
|
||||
- current command correlation id,
|
||||
- pending command count,
|
||||
- event queue depth,
|
||||
- restart/kill reason if terminal.
|
||||
|
||||
### Events Page
|
||||
|
||||
Show aggregate event diagnostics:
|
||||
|
||||
- event rate by session,
|
||||
- event rate by event family,
|
||||
- total events since start,
|
||||
- queue overflow count,
|
||||
- stream disconnect count,
|
||||
- recent terminal faults.
|
||||
|
||||
Do not display full tag values by default. If value display is later added, make
|
||||
it opt-in and redacted.
|
||||
|
||||
### Settings Page
|
||||
|
||||
Show read-only effective configuration:
|
||||
|
||||
- worker executable path,
|
||||
- configured timeouts,
|
||||
- queue capacities,
|
||||
- auth mode,
|
||||
- SQLite auth database path with sensitive parts redacted if needed,
|
||||
- dashboard enabled state,
|
||||
- protocol version.
|
||||
|
||||
Do not show API key secrets or pepper values.
|
||||
|
||||
## Authentication And Authorization
|
||||
|
||||
Dashboard access should use the same API-key authentication model as gRPC where
|
||||
practical.
|
||||
|
||||
Recommended v1 behavior:
|
||||
|
||||
- dashboard disabled by default unless configured,
|
||||
- when enabled, require API key auth,
|
||||
- require `admin` scope for dashboard access,
|
||||
- accept API key through a secure cookie established by a simple login form, or
|
||||
through reverse-proxy/header configuration for local deployments,
|
||||
- do not put API keys in query strings.
|
||||
|
||||
Simplest implementation path:
|
||||
|
||||
1. Add `/dashboard/login`.
|
||||
2. User submits API key over HTTPS.
|
||||
3. Gateway validates key and `admin` scope.
|
||||
4. Gateway issues an HTTP-only secure auth cookie for the dashboard.
|
||||
5. Dashboard pages require that cookie.
|
||||
6. Logout clears the cookie.
|
||||
|
||||
For local development, allow an explicit `Dashboard:AllowAnonymousLocalhost`
|
||||
option. It must default to false.
|
||||
|
||||
## Configuration
|
||||
|
||||
Suggested configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"MxGateway": {
|
||||
"Dashboard": {
|
||||
"Enabled": true,
|
||||
"PathBase": "/dashboard",
|
||||
"RequireAdminScope": true,
|
||||
"AllowAnonymousLocalhost": false,
|
||||
"SnapshotIntervalMilliseconds": 1000,
|
||||
"RecentFaultLimit": 100,
|
||||
"RecentSessionLimit": 200,
|
||||
"ShowTagValues": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Rules
|
||||
|
||||
- Do not display API key secrets.
|
||||
- Do not display credential-bearing MXAccess command values.
|
||||
- Do not display full tag values by default.
|
||||
- Do not expose worker pipe names with nonce or sensitive details.
|
||||
- Protect dashboard auth cookies with `HttpOnly`, `Secure`, and `SameSite`.
|
||||
- Require TLS for remote dashboard access.
|
||||
- Use anti-forgery protection for login/logout and any future admin actions.
|
||||
|
||||
## Styling
|
||||
|
||||
Use Bootstrap utility classes and a small local stylesheet.
|
||||
|
||||
Recommended visual language:
|
||||
|
||||
- compact tables,
|
||||
- status badges,
|
||||
- metric cards,
|
||||
- Bootstrap alerts for faults,
|
||||
- restrained colors,
|
||||
- no decorative hero sections,
|
||||
- no charting dependency for v1.
|
||||
|
||||
If charts are added later, prefer simple server-generated data tables first. Do
|
||||
not add a JavaScript charting dependency without a specific need.
|
||||
|
||||
## Testing
|
||||
|
||||
Dashboard unit/component tests should cover:
|
||||
|
||||
- snapshot projection,
|
||||
- dashboard auth authorization decisions,
|
||||
- login API-key validation behavior,
|
||||
- pages render with empty state,
|
||||
- pages render with active sessions,
|
||||
- pages render with faulted sessions,
|
||||
- realtime subscription disposal,
|
||||
- redaction of API keys and credential values.
|
||||
|
||||
Use bUnit if component testing is added. Otherwise keep the first tests focused
|
||||
on snapshot services and authorization logic.
|
||||
|
||||
Integration tests should verify:
|
||||
|
||||
- dashboard disabled returns not found or configured fallback,
|
||||
- dashboard requires auth when enabled,
|
||||
- admin-scoped key can access dashboard,
|
||||
- non-admin key is denied,
|
||||
- live snapshot updates when a fake session changes state.
|
||||
|
||||
## Initial Implementation Slice
|
||||
|
||||
The first dashboard slice should implement:
|
||||
|
||||
1. Blazor Server hosting in `MxGateway.Server`.
|
||||
2. Bootstrap static assets.
|
||||
3. dashboard configuration binding.
|
||||
4. dashboard auth using API key login and HTTP-only cookie.
|
||||
5. read-only `DashboardSnapshotService`.
|
||||
6. home page with metric cards.
|
||||
7. sessions page with active session table.
|
||||
8. workers page with worker table.
|
||||
9. 1-second realtime refresh through Blazor Server.
|
||||
10. redaction tests for secrets.
|
||||
|
||||
@@ -0,0 +1,774 @@
|
||||
# Gateway Process Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
The gateway process is the only public network-facing component. It exposes the
|
||||
modern API, owns session lifecycle, launches and supervises MXAccess worker
|
||||
processes, and moves commands and events between clients and the worker that
|
||||
owns each session.
|
||||
|
||||
The gateway must not instantiate MXAccess COM, import MXAccess interop types, or
|
||||
depend on an STA message pump. The installed MXAccess COM component is isolated
|
||||
behind the worker process boundary.
|
||||
|
||||
## Runtime
|
||||
|
||||
- Target runtime: .NET 10.
|
||||
- Language: C#.
|
||||
- Preferred process architecture: x64.
|
||||
- Hosting: ASP.NET Core gRPC.
|
||||
- Web UI: Blazor Server dashboard with Bootstrap CSS/JS.
|
||||
- Operating system: Windows.
|
||||
- Public transport: TCP gRPC.
|
||||
- Internal worker transport: named pipes with protobuf-framed messages.
|
||||
|
||||
Style guides:
|
||||
|
||||
- [C# Style Guide](./style-guides/CSharpStyleGuide.md)
|
||||
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
|
||||
|
||||
## Responsibilities
|
||||
|
||||
The gateway owns:
|
||||
|
||||
- public gRPC service endpoints,
|
||||
- Blazor Server dashboard endpoints,
|
||||
- optional authentication and authorization,
|
||||
- session id allocation,
|
||||
- worker executable selection,
|
||||
- named-pipe server creation,
|
||||
- worker process launch,
|
||||
- gateway/worker handshake,
|
||||
- command correlation and timeout handling,
|
||||
- event fan-out to client streams,
|
||||
- session lease and heartbeat enforcement,
|
||||
- worker crash and hang detection,
|
||||
- metrics and structured logging,
|
||||
- graceful service shutdown.
|
||||
|
||||
The gateway does not own:
|
||||
|
||||
- MXAccess COM object creation,
|
||||
- MXAccess method dispatch,
|
||||
- MXAccess event subscription,
|
||||
- MXAccess handle generation,
|
||||
- COM value conversion from native `VARIANT` values.
|
||||
|
||||
Those belong to the worker.
|
||||
|
||||
## High-Level Components
|
||||
|
||||
```text
|
||||
MxGateway.Server
|
||||
Program / Host
|
||||
Configuration
|
||||
Grpc
|
||||
MxAccessGatewayService
|
||||
RequestReplyMapper
|
||||
EventMapper
|
||||
Dashboard
|
||||
Pages
|
||||
Components
|
||||
DashboardSnapshotService
|
||||
DashboardAuthorization
|
||||
Sessions
|
||||
SessionManager
|
||||
GatewaySession
|
||||
SessionRegistry
|
||||
SessionLeaseMonitor
|
||||
Workers
|
||||
WorkerProcessLauncher
|
||||
WorkerClient
|
||||
WorkerPipeTransport
|
||||
WorkerProtocolReader
|
||||
WorkerProtocolWriter
|
||||
WorkerWatchdog
|
||||
Security
|
||||
ClientIdentityResolver
|
||||
CommandAuthorization
|
||||
Metrics
|
||||
GatewayMetrics
|
||||
Diagnostics
|
||||
HealthChecks
|
||||
```
|
||||
|
||||
## Public gRPC Surface
|
||||
|
||||
Start with unary commands plus an event stream:
|
||||
|
||||
```protobuf
|
||||
service MxAccessGateway {
|
||||
rpc OpenSession(OpenSessionRequest) returns (OpenSessionReply);
|
||||
rpc CloseSession(CloseSessionRequest) returns (CloseSessionReply);
|
||||
rpc Invoke(MxCommandRequest) returns (MxCommandReply);
|
||||
rpc StreamEvents(StreamEventsRequest) returns (stream MxEvent);
|
||||
}
|
||||
```
|
||||
|
||||
Add this later only after the command and event model is stable:
|
||||
|
||||
```protobuf
|
||||
rpc Session(stream ClientMessage) returns (stream ServerMessage);
|
||||
```
|
||||
|
||||
### OpenSession
|
||||
|
||||
`OpenSession` creates one gateway session and one worker process by default.
|
||||
|
||||
Inputs should include:
|
||||
|
||||
- requested backend, defaulting to `mxaccess-worker`,
|
||||
- optional client session name,
|
||||
- optional client correlation id,
|
||||
- optional timeout policy,
|
||||
- optional event backpressure policy,
|
||||
- optional metadata discovery options.
|
||||
|
||||
Outputs should include:
|
||||
|
||||
- session id,
|
||||
- backend name,
|
||||
- worker process id when available,
|
||||
- protocol version,
|
||||
- server capabilities,
|
||||
- default timeout values.
|
||||
|
||||
Behavior:
|
||||
|
||||
1. Resolve and authorize the client identity.
|
||||
2. Allocate a session id.
|
||||
3. Build a pipe name and random handshake nonce.
|
||||
4. Create a named-pipe server with restrictive local ACLs.
|
||||
5. Launch the worker executable with session bootstrap data.
|
||||
6. Accept the pipe connection within startup timeout.
|
||||
7. Exchange `GatewayHello` and `WorkerHello`.
|
||||
8. Wait for `WorkerReady`.
|
||||
9. Register the session as ready.
|
||||
10. Return the session details.
|
||||
|
||||
If any step fails, clean up all resources. Kill the worker if it was launched
|
||||
and did not shut down on its own.
|
||||
|
||||
### CloseSession
|
||||
|
||||
`CloseSession` attempts graceful shutdown and then enforces a kill timeout.
|
||||
|
||||
Behavior:
|
||||
|
||||
1. Mark the session closing.
|
||||
2. Stop accepting new commands.
|
||||
3. Notify event streams of terminal session close.
|
||||
4. Send `WorkerShutdown` when the pipe is still connected.
|
||||
5. Wait for worker exit up to the configured timeout.
|
||||
6. Kill the worker process if it remains alive.
|
||||
7. Remove the session from the registry.
|
||||
|
||||
`CloseSession` should be idempotent. Closing an already closed session should
|
||||
return a successful close result with the final known state.
|
||||
|
||||
### Invoke
|
||||
|
||||
`Invoke` forwards one MXAccess command to the worker that owns the session.
|
||||
|
||||
Behavior:
|
||||
|
||||
1. Validate the session id.
|
||||
2. Check session state is `Ready`.
|
||||
3. Validate the method-specific payload.
|
||||
4. Authorize the command, especially writes and credential-bearing commands.
|
||||
5. Assign a gateway correlation id.
|
||||
6. Write `WorkerCommand` to the worker pipe.
|
||||
7. Await the correlated `WorkerCommandReply`.
|
||||
8. Map worker reply to public `MxCommandReply`.
|
||||
|
||||
Request cancellation stops waiting in the gateway. It does not abort an
|
||||
in-flight COM call. If the command must be hard-canceled, kill the worker and
|
||||
fault the session.
|
||||
|
||||
### StreamEvents
|
||||
|
||||
`StreamEvents` streams events for one session.
|
||||
|
||||
Initial implementation allows one active stream subscriber per session. A second
|
||||
subscriber should be rejected with a clear session error. If multiple
|
||||
subscribers are later supported, they must have independent backpressure
|
||||
accounting and a clear fan-out policy.
|
||||
|
||||
Behavior:
|
||||
|
||||
1. Validate session id and authorize event access.
|
||||
2. Attach a stream cursor to the session event channel.
|
||||
3. Send events in worker sequence order.
|
||||
4. Stop on client cancellation, session close, or session fault.
|
||||
5. Emit a terminal status when the session faults if gRPC status alone cannot
|
||||
preserve the required details.
|
||||
|
||||
The gateway must not reorder events from one worker.
|
||||
|
||||
## Web Dashboard
|
||||
|
||||
The gateway hosts a basic Blazor Server dashboard for operators and developers.
|
||||
The dashboard is read-only for v1 and should show current gateway/session/worker
|
||||
state plus basic metrics.
|
||||
|
||||
Technology:
|
||||
|
||||
- Blazor Server,
|
||||
- Bootstrap CSS,
|
||||
- Bootstrap JavaScript,
|
||||
- no MudBlazor,
|
||||
- no other Blazor client component libraries.
|
||||
|
||||
Suggested routes:
|
||||
|
||||
```text
|
||||
/dashboard
|
||||
/dashboard/sessions
|
||||
/dashboard/sessions/{sessionId}
|
||||
/dashboard/workers
|
||||
/dashboard/events
|
||||
/dashboard/settings
|
||||
```
|
||||
|
||||
Dashboard pages:
|
||||
|
||||
- home: gateway status, uptime, session count, worker count, command rate,
|
||||
event rate, queue depth, recent faults,
|
||||
- sessions: active/recent session table,
|
||||
- session details: one session's worker, heartbeat, counters, queues, and fault
|
||||
summary,
|
||||
- workers: worker process table and heartbeat details,
|
||||
- events: aggregate event counters and rates,
|
||||
- settings: read-only effective configuration with secrets redacted.
|
||||
|
||||
Realtime updates should use Blazor Server component updates from a read-only
|
||||
snapshot service. Components should subscribe to snapshots and call
|
||||
`StateHasChanged` through `InvokeAsync`. Do not stream every MXAccess event to
|
||||
the dashboard; aggregate event rates and counters instead.
|
||||
|
||||
Suggested service shape:
|
||||
|
||||
```csharp
|
||||
public interface IDashboardSnapshotService
|
||||
{
|
||||
DashboardSnapshot GetSnapshot();
|
||||
IAsyncEnumerable<DashboardSnapshot> WatchSnapshotsAsync(
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
Default refresh policy:
|
||||
|
||||
- immediate update on session create, close, or fault,
|
||||
- immediate update on worker fault,
|
||||
- periodic metrics refresh every 1 second,
|
||||
- event-rate windows updated every 1 second.
|
||||
|
||||
Dashboard access should require API-key-backed authentication with `admin` scope
|
||||
when enabled. A simple `/dashboard/login` form can validate an API key and issue
|
||||
an HTTP-only secure cookie for dashboard pages. Do not put API keys in query
|
||||
strings. Anonymous localhost access may exist only behind an explicit
|
||||
configuration option that defaults to false.
|
||||
|
||||
## Session State Machine
|
||||
|
||||
```text
|
||||
Creating
|
||||
-> StartingWorker
|
||||
-> WaitingForPipe
|
||||
-> Handshaking
|
||||
-> InitializingWorker
|
||||
-> Ready
|
||||
-> Closing
|
||||
-> Closed
|
||||
|
||||
Any non-terminal state
|
||||
-> Faulted
|
||||
|
||||
Faulted
|
||||
-> Closed
|
||||
```
|
||||
|
||||
### State Rules
|
||||
|
||||
- `Creating`: session id and in-memory state exist, but no worker has launched.
|
||||
- `StartingWorker`: worker process launch is in progress.
|
||||
- `WaitingForPipe`: gateway is waiting for the worker to connect to the pipe.
|
||||
- `Handshaking`: pipe is connected and protocol hello is being verified.
|
||||
- `InitializingWorker`: worker is connected but has not reported MXAccess ready.
|
||||
- `Ready`: commands and event streams may run.
|
||||
- `Closing`: graceful shutdown is in progress.
|
||||
- `Closed`: resources are released.
|
||||
- `Faulted`: a non-graceful terminal fault occurred and must be reported to
|
||||
callers before resources are released.
|
||||
|
||||
Only `Ready` sessions accept new commands.
|
||||
|
||||
## Session Model
|
||||
|
||||
Gateway session state should include:
|
||||
|
||||
- session id,
|
||||
- client identity,
|
||||
- backend name,
|
||||
- worker process id,
|
||||
- worker executable path and version,
|
||||
- pipe name,
|
||||
- pipe connection state,
|
||||
- open time,
|
||||
- last client activity time,
|
||||
- last worker heartbeat time,
|
||||
- lease expiration,
|
||||
- command timeout policy,
|
||||
- startup timeout policy,
|
||||
- shutdown timeout policy,
|
||||
- event queue metrics,
|
||||
- active event stream count,
|
||||
- final fault if any.
|
||||
|
||||
The worker remains authoritative for MXAccess handles. The gateway may keep a
|
||||
shadow state for diagnostics, but it must not invent, rewrite, or recycle
|
||||
MXAccess handles.
|
||||
|
||||
## Worker Launch
|
||||
|
||||
The gateway should launch the worker using explicit configuration:
|
||||
|
||||
- worker executable path,
|
||||
- worker working directory,
|
||||
- worker architecture requirement,
|
||||
- protocol version,
|
||||
- startup timeout,
|
||||
- environment variables,
|
||||
- optional restricted user identity.
|
||||
|
||||
Command-line arguments should include only non-secret bootstrap values:
|
||||
|
||||
```text
|
||||
--session-id <sessionId>
|
||||
--pipe-name <pipeName>
|
||||
--protocol-version <version>
|
||||
```
|
||||
|
||||
Prefer passing the handshake nonce via inherited environment or another
|
||||
protected local mechanism instead of command line when possible.
|
||||
|
||||
Before launch, validate:
|
||||
|
||||
- worker executable exists,
|
||||
- worker path is under the configured install directory,
|
||||
- worker file version or product version is acceptable,
|
||||
- worker is expected to be x86.
|
||||
|
||||
## Worker IPC
|
||||
|
||||
The gateway creates the pipe server before launching the worker.
|
||||
|
||||
Pipe name:
|
||||
|
||||
```text
|
||||
mxaccess-gateway-{gatewayProcessId}-{sessionId}
|
||||
```
|
||||
|
||||
Message framing:
|
||||
|
||||
```text
|
||||
uint32 little-endian payload_length
|
||||
payload_length bytes protobuf WorkerEnvelope
|
||||
```
|
||||
|
||||
Recommended size limits:
|
||||
|
||||
- default max message size: 16 MiB,
|
||||
- configurable upper bound for large arrays,
|
||||
- reject zero-length payloads,
|
||||
- reject payloads larger than configured maximum before allocation.
|
||||
|
||||
### Envelope Rules
|
||||
|
||||
Every message uses `WorkerEnvelope`:
|
||||
|
||||
- `protocol_version` must match a supported version.
|
||||
- `session_id` must match the pipe/session.
|
||||
- `sequence` is monotonic per sender.
|
||||
- `correlation_id` links commands and replies.
|
||||
- events use either zero or their own event correlation id.
|
||||
- protocol faults do not replace MXAccess HRESULT/status details.
|
||||
|
||||
The gateway should treat malformed frames, sequence regressions, and wrong
|
||||
session ids as protocol faults and close the session.
|
||||
|
||||
## WorkerClient Design
|
||||
|
||||
`WorkerClient` is the gateway-side object that owns one worker connection.
|
||||
|
||||
Suggested public shape:
|
||||
|
||||
```csharp
|
||||
public interface IWorkerClient : IAsyncDisposable
|
||||
{
|
||||
string SessionId { get; }
|
||||
int? ProcessId { get; }
|
||||
WorkerClientState State { get; }
|
||||
|
||||
Task StartAsync(CancellationToken cancellationToken);
|
||||
Task<WorkerCommandReply> InvokeAsync(
|
||||
WorkerCommand command,
|
||||
TimeSpan timeout,
|
||||
CancellationToken cancellationToken);
|
||||
IAsyncEnumerable<WorkerEvent> ReadEventsAsync(
|
||||
CancellationToken cancellationToken);
|
||||
Task ShutdownAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
void Kill(string reason);
|
||||
}
|
||||
```
|
||||
|
||||
Internally it owns:
|
||||
|
||||
- process handle,
|
||||
- pipe stream,
|
||||
- read loop,
|
||||
- write loop,
|
||||
- bounded outbound command/control channel,
|
||||
- bounded inbound event channel,
|
||||
- pending command dictionary keyed by correlation id,
|
||||
- heartbeat monitor,
|
||||
- terminal fault source.
|
||||
|
||||
### Read Loop
|
||||
|
||||
The read loop:
|
||||
|
||||
1. Reads one frame.
|
||||
2. Parses `WorkerEnvelope`.
|
||||
3. Validates protocol fields.
|
||||
4. Dispatches by body type:
|
||||
- `WorkerCommandReply`: completes pending command.
|
||||
- `WorkerEvent`: enqueues event.
|
||||
- `WorkerHeartbeat`: updates heartbeat timestamp.
|
||||
- `WorkerFault`: faults session.
|
||||
5. Stops when pipe closes or cancellation is requested.
|
||||
|
||||
If the pipe closes while the session is not closing, fault the session.
|
||||
|
||||
### Write Loop
|
||||
|
||||
The write loop serializes all writes to the pipe. No other code should write to
|
||||
the pipe directly.
|
||||
|
||||
It handles:
|
||||
|
||||
- `GatewayHello`,
|
||||
- `WorkerCommand`,
|
||||
- `WorkerCancel`,
|
||||
- `WorkerShutdown`,
|
||||
- gateway heartbeat if used.
|
||||
|
||||
The write loop should fail the session if a pipe write fails outside normal
|
||||
shutdown.
|
||||
|
||||
## Command Correlation
|
||||
|
||||
Each command gets:
|
||||
|
||||
- gateway correlation id,
|
||||
- method name,
|
||||
- start timestamp,
|
||||
- timeout deadline,
|
||||
- caller cancellation token,
|
||||
- reply completion source.
|
||||
|
||||
Pending command handling:
|
||||
|
||||
- Add the pending entry before writing the command.
|
||||
- Remove it exactly once when reply, timeout, cancellation, or session fault
|
||||
occurs.
|
||||
- If a late reply arrives after cancellation or timeout, log it with the
|
||||
correlation id and discard it.
|
||||
- If the session faults, complete all pending commands with a structured fault.
|
||||
|
||||
Timeouts should not assume the COM call stopped. A timed-out command may still
|
||||
finish inside the worker.
|
||||
|
||||
## Fault Model
|
||||
|
||||
Fault categories:
|
||||
|
||||
- `StartupFailed`
|
||||
- `ProtocolMismatch`
|
||||
- `ProtocolViolation`
|
||||
- `PipeDisconnected`
|
||||
- `WorkerExited`
|
||||
- `HeartbeatExpired`
|
||||
- `CommandTimeout`
|
||||
- `WorkerFaulted`
|
||||
- `GatewayShutdown`
|
||||
- `AuthorizationFailed`
|
||||
|
||||
Public replies should distinguish:
|
||||
|
||||
- gRPC transport failure,
|
||||
- gateway/session failure,
|
||||
- worker protocol failure,
|
||||
- MXAccess method failure,
|
||||
- MXAccess HRESULT/status failure.
|
||||
|
||||
Do not hide an MXAccess HRESULT by returning only an RPC error. When MXAccess
|
||||
was reached and returned status, preserve that status in the command reply.
|
||||
|
||||
## Heartbeats And Leases
|
||||
|
||||
Use separate concepts:
|
||||
|
||||
- worker heartbeat: proves the worker process and pipe loop are alive,
|
||||
- session lease: proves the client still owns the session,
|
||||
- command timeout: bounds one command wait,
|
||||
- startup timeout: bounds worker creation,
|
||||
- shutdown timeout: bounds graceful stop.
|
||||
|
||||
Suggested defaults for early development:
|
||||
|
||||
- startup timeout: 30 seconds,
|
||||
- worker heartbeat interval: 5 seconds,
|
||||
- heartbeat grace: 15 seconds,
|
||||
- default command timeout: 30 seconds,
|
||||
- graceful shutdown timeout: 10 seconds,
|
||||
- idle session lease: configurable, disabled in local development.
|
||||
|
||||
The exact values should be configurable.
|
||||
|
||||
## Event Delivery
|
||||
|
||||
Events flow:
|
||||
|
||||
```text
|
||||
worker MXAccess event
|
||||
-> worker outbound event queue
|
||||
-> worker pipe writer
|
||||
-> gateway read loop
|
||||
-> session event channel
|
||||
-> gRPC StreamEvents
|
||||
```
|
||||
|
||||
The gateway should record:
|
||||
|
||||
- worker event sequence,
|
||||
- gateway receive sequence,
|
||||
- worker timestamp,
|
||||
- gateway receive timestamp,
|
||||
- stream send timestamp if needed for diagnostics.
|
||||
|
||||
Default backpressure policy for parity testing should be fail-fast:
|
||||
|
||||
1. If the session event channel fills, fault the session.
|
||||
2. Preserve the overflow details in logs and metrics.
|
||||
3. Do not silently drop data-change events.
|
||||
|
||||
Do not set a production event-rate target before measurement. Emit event rate,
|
||||
queue depth, stream send latency, and overflow metrics. Later production modes
|
||||
may support explicit coalescing by item handle as an opt-in behavior.
|
||||
|
||||
The gateway should not synthesize `OperationComplete` from write completion,
|
||||
command replies, ASB completion queues, or completion-only status frames. Forward
|
||||
`OperationComplete` only when the worker reports the native MXAccess public
|
||||
event.
|
||||
|
||||
## Security
|
||||
|
||||
### Public API
|
||||
|
||||
Use API key authentication for v1. Store API keys in a gateway-owned SQLite
|
||||
database, but store only hashed key secrets. Clients should send keys in gRPC
|
||||
metadata using:
|
||||
|
||||
```text
|
||||
authorization: Bearer mxgw_<key-id>_<secret>
|
||||
```
|
||||
|
||||
The gateway should split the key into a stable key id and secret component,
|
||||
load the key record by id, hash the presented secret, and compare using a
|
||||
constant-time comparison.
|
||||
|
||||
Recommended scopes:
|
||||
|
||||
- `session:open`
|
||||
- `session:close`
|
||||
- `invoke:read`
|
||||
- `invoke:write`
|
||||
- `invoke:secure`
|
||||
- `events:read`
|
||||
- `metadata:read`
|
||||
- `admin`
|
||||
|
||||
If the gateway is exposed outside the local machine, use TLS. Do not log raw API
|
||||
keys or raw credential-bearing MXAccess values.
|
||||
|
||||
API key administration for v1 should be a local CLI/tool rather than a public
|
||||
gRPC admin API. It should initialize the auth database, create keys, list keys
|
||||
without secrets, revoke keys, rotate keys, and print raw secrets only once at
|
||||
creation.
|
||||
|
||||
SQLite auth storage should use startup migrations with a `schema_version` table.
|
||||
Migrations should run inside transactions and fail startup if the database
|
||||
schema is newer than the running binary understands.
|
||||
|
||||
Commands requiring authorization:
|
||||
|
||||
- writes,
|
||||
- secured writes,
|
||||
- authentication commands,
|
||||
- worker shutdown diagnostics,
|
||||
- metadata queries if they expose sensitive plant structure.
|
||||
|
||||
### Worker IPC
|
||||
|
||||
Named pipes should be local only. Pipe ACLs should restrict access to:
|
||||
|
||||
- the gateway process identity,
|
||||
- the launched worker identity,
|
||||
- administrators only when operationally required.
|
||||
|
||||
The worker must validate `GatewayHello` and the nonce before creating MXAccess.
|
||||
|
||||
## Observability
|
||||
|
||||
Use structured logs with these fields where applicable:
|
||||
|
||||
- session id,
|
||||
- client identity,
|
||||
- worker process id,
|
||||
- pipe name hash or suffix,
|
||||
- protocol version,
|
||||
- correlation id,
|
||||
- command method,
|
||||
- MXAccess HRESULT,
|
||||
- MXAccess status summary,
|
||||
- event family,
|
||||
- event sequence,
|
||||
- queue depth,
|
||||
- elapsed milliseconds.
|
||||
|
||||
Metrics:
|
||||
|
||||
- open sessions,
|
||||
- workers running,
|
||||
- worker startup latency,
|
||||
- command latency by method,
|
||||
- command failures by method and category,
|
||||
- event rate by session and family,
|
||||
- event queue depth,
|
||||
- worker exits by reason,
|
||||
- worker kills,
|
||||
- heartbeat failures,
|
||||
- gRPC stream disconnects.
|
||||
|
||||
Do not log credential values or full tag values by default.
|
||||
|
||||
## Configuration
|
||||
|
||||
Suggested configuration shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"MxGateway": {
|
||||
"Authentication": {
|
||||
"Mode": "ApiKey",
|
||||
"SqlitePath": "C:\\ProgramData\\MxGateway\\gateway-auth.db",
|
||||
"PepperSecretName": "MxGateway:ApiKeyPepper",
|
||||
"RunMigrationsOnStartup": true
|
||||
},
|
||||
"Worker": {
|
||||
"ExecutablePath": "src/MxGateway.Worker/bin/x86/Release/MxGateway.Worker.exe",
|
||||
"StartupTimeoutSeconds": 30,
|
||||
"ShutdownTimeoutSeconds": 10,
|
||||
"HeartbeatIntervalSeconds": 5,
|
||||
"HeartbeatGraceSeconds": 15,
|
||||
"MaxMessageBytes": 16777216
|
||||
},
|
||||
"Sessions": {
|
||||
"DefaultCommandTimeoutSeconds": 30,
|
||||
"MaxSessions": 64,
|
||||
"AllowMultipleEventSubscribers": false
|
||||
},
|
||||
"Events": {
|
||||
"QueueCapacity": 10000,
|
||||
"BackpressurePolicy": "FailFast"
|
||||
},
|
||||
"Dashboard": {
|
||||
"Enabled": true,
|
||||
"PathBase": "/dashboard",
|
||||
"RequireAdminScope": true,
|
||||
"AllowAnonymousLocalhost": false,
|
||||
"SnapshotIntervalMilliseconds": 1000,
|
||||
"RecentFaultLimit": 100,
|
||||
"RecentSessionLimit": 200,
|
||||
"ShowTagValues": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Do not scatter connection or path constants through implementation code.
|
||||
|
||||
## Galaxy Repository Metadata
|
||||
|
||||
Galaxy hierarchy and tag metadata can be discovered through SQL Server when
|
||||
needed for browse or diagnostics. The current notes live outside this repo at:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\lmxopcua\gr
|
||||
```
|
||||
|
||||
Use SQL metadata as discovery data. It does not replace MXAccess-backed runtime
|
||||
behavior unless an explicit non-parity backend is designed.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Gateway tests should be able to run without installed MXAccess by using fake
|
||||
workers and fake transports.
|
||||
|
||||
Focused tests:
|
||||
|
||||
- session state transitions,
|
||||
- worker startup failures,
|
||||
- protocol version mismatch,
|
||||
- malformed frame handling,
|
||||
- pending command completion,
|
||||
- command timeout and late reply handling,
|
||||
- worker crash handling,
|
||||
- event ordering,
|
||||
- event queue overflow,
|
||||
- `CloseSession` idempotency,
|
||||
- gRPC mapping for command replies and faults.
|
||||
- dashboard snapshot projection,
|
||||
- dashboard auth decisions,
|
||||
- dashboard redaction,
|
||||
- dashboard realtime subscription disposal.
|
||||
|
||||
Integration tests with the real worker should be separated from unit tests and
|
||||
clearly marked because they require Windows, .NET Framework worker output, and
|
||||
eventually installed MXAccess COM.
|
||||
|
||||
## Initial Implementation Slice
|
||||
|
||||
The first gateway slice should implement:
|
||||
|
||||
1. Host startup and configuration binding.
|
||||
2. SQLite auth database initialization and migrations.
|
||||
3. Local API-key administration CLI/tool.
|
||||
4. API-key authentication and scope checks.
|
||||
5. `OpenSession`.
|
||||
6. Worker process launch.
|
||||
7. Named-pipe handshake.
|
||||
8. `Invoke` for `Register`, `AddItem`, and `Advise`.
|
||||
9. `StreamEvents` with one subscriber per session.
|
||||
10. `CloseSession`.
|
||||
11. Worker crash and startup failure handling.
|
||||
12. Event-rate, queue-depth, and overflow metrics.
|
||||
13. Blazor Server dashboard with Bootstrap assets.
|
||||
14. Dashboard home, sessions, and workers pages.
|
||||
15. Dashboard realtime snapshot refresh.
|
||||
16. Dashboard API-key login with admin-scope check.
|
||||
17. Basic structured logs.
|
||||
|
||||
This proves the process model before the full command surface is implemented.
|
||||
@@ -0,0 +1,387 @@
|
||||
# Client Libraries Implementation Plan
|
||||
|
||||
This plan implements the official gRPC clients after the gateway and worker
|
||||
first slice is stable enough to generate contracts and run smoke tests.
|
||||
|
||||
Primary designs:
|
||||
|
||||
- `docs/client-libraries-design.md`
|
||||
- `docs/clients-dotnet-csharp-design.md`
|
||||
- `docs/clients-golang-design.md`
|
||||
- `docs/clients-rust-design.md`
|
||||
- `docs/clients-python-design.md`
|
||||
- `docs/clients-java-design.md`
|
||||
- `docs/toolchain-links.md`
|
||||
|
||||
## Shared Milestone: client-contracts-and-fixtures
|
||||
|
||||
Goal: make client implementations consistent across languages.
|
||||
|
||||
### Issue: Publish Stable Client Proto Generation Inputs
|
||||
|
||||
Labels: `area:contracts`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- finalized v1 `.proto` files,
|
||||
- Buf config if used,
|
||||
- generation documentation for all languages,
|
||||
- generated-code output directories,
|
||||
- golden protobuf payload fixtures.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- C#, Go, Rust, Python, and Java generated code can be regenerated,
|
||||
- generated code is not hand-edited,
|
||||
- protocol version is visible to clients.
|
||||
|
||||
### Issue: Create Cross-Language Client Behavior Fixtures
|
||||
|
||||
Labels: `area:tests`, `type:test`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- JSON fixtures for command replies,
|
||||
- JSON fixtures for event stream samples,
|
||||
- value conversion fixtures,
|
||||
- status conversion fixtures,
|
||||
- auth error fixtures,
|
||||
- timeout/cancel expected behavior notes.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- every language can use the same fixture set,
|
||||
- fixtures include raw fallback values,
|
||||
- fixtures include MXAccess status arrays and HRESULT.
|
||||
|
||||
## Milestone: clients-dotnet
|
||||
|
||||
Goal: implement the .NET 10 C# client library, test CLI, and tests.
|
||||
|
||||
### Issue: Scaffold .NET Client Projects
|
||||
|
||||
Labels: `area:client-dotnet`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `clients/dotnet/MxGateway.Client`,
|
||||
- `clients/dotnet/MxGateway.Client.Cli`,
|
||||
- `clients/dotnet/MxGateway.Client.Tests`,
|
||||
- optional integration test project,
|
||||
- generated protobuf setup.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- `dotnet build` succeeds,
|
||||
- generated gRPC client code compiles,
|
||||
- empty tests run.
|
||||
|
||||
### Issue: Implement .NET GatewayClient And Session
|
||||
|
||||
Labels: `area:client-dotnet`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `MxGatewayClientOptions`,
|
||||
- `MxGatewayClient`,
|
||||
- `MxGatewaySession`,
|
||||
- raw `OpenSession`, `CloseSession`, `Invoke`, `StreamEvents`,
|
||||
- helpers for `Register`, `AddItem`, `AddItem2`, `Advise`, `Write`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- API key metadata is attached,
|
||||
- cancellation token flows to every call,
|
||||
- raw replies remain accessible,
|
||||
- session close is explicit and idempotent.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake gRPC service,
|
||||
- helper request construction,
|
||||
- cancellation.
|
||||
|
||||
### Issue: Implement .NET Values, Status, Errors, And CLI
|
||||
|
||||
Labels: `area:client-dotnet`, `type:feature`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `MxValue` helper conversions,
|
||||
- status proxy helpers,
|
||||
- typed exceptions,
|
||||
- `EnsureProtocolSuccess`,
|
||||
- `EnsureMxAccessSuccess`,
|
||||
- CLI commands: version, ping, open-session, close-session, register,
|
||||
add-item, advise, stream-events, write, write2, smoke,
|
||||
- JSON CLI output.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- scalar and array conversions pass fixtures,
|
||||
- status arrays are preserved,
|
||||
- API keys are redacted,
|
||||
- smoke command closes session in `finally`.
|
||||
|
||||
## Milestone: clients-go
|
||||
|
||||
Goal: implement the Go module, test CLI, and tests.
|
||||
|
||||
### Issue: Scaffold Go Module
|
||||
|
||||
Labels: `area:client-go`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `clients/go/go.mod`,
|
||||
- generated protobuf package,
|
||||
- `mxgateway` package,
|
||||
- `cmd/mxgw-go`,
|
||||
- unit test structure.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- `go test ./...` runs,
|
||||
- generated code compiles,
|
||||
- module path is stable.
|
||||
|
||||
### Issue: Implement Go Client, Session, Values, Errors, And CLI
|
||||
|
||||
Labels: `area:client-go`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `Dial(ctx, Options)`,
|
||||
- auth interceptors,
|
||||
- TLS/plaintext setup,
|
||||
- `Client.OpenSession`,
|
||||
- `Session` helpers,
|
||||
- event channel receive loop,
|
||||
- value conversion helpers,
|
||||
- typed errors with `errors.As`,
|
||||
- CLI commands and JSON output.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- auth metadata on unary and streams,
|
||||
- context cancellation stops calls,
|
||||
- event channel closes exactly once,
|
||||
- raw protobuf access remains available,
|
||||
- fixture conversions pass,
|
||||
- CLI redacts API key.
|
||||
|
||||
Tests:
|
||||
|
||||
- `bufconn` fake service,
|
||||
- auth metadata,
|
||||
- stream cancellation,
|
||||
- conversion fixtures,
|
||||
- CLI parser/output.
|
||||
|
||||
## Milestone: clients-rust
|
||||
|
||||
Goal: implement the Rust `tonic` client crate, test CLI, and tests.
|
||||
|
||||
### Issue: Scaffold Rust Workspace
|
||||
|
||||
Labels: `area:client-rust`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `clients/rust/Cargo.toml`,
|
||||
- client crate,
|
||||
- CLI crate,
|
||||
- `build.rs` protobuf generation,
|
||||
- generated module organization.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- `cargo test` runs,
|
||||
- generated code compiles,
|
||||
- MSVC linker works.
|
||||
|
||||
### Issue: Implement Rust Client, Session, Values, Errors, And CLI
|
||||
|
||||
Labels: `area:client-rust`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `ClientOptions`,
|
||||
- `GatewayClient::connect`,
|
||||
- auth interceptor,
|
||||
- TLS/plaintext channel,
|
||||
- session helpers,
|
||||
- event stream as `Stream<Item = Result<MxEvent, Error>>`,
|
||||
- `thiserror` error model,
|
||||
- conversion helpers,
|
||||
- `clap` CLI,
|
||||
- JSON output with `serde_json`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- metadata includes bearer key,
|
||||
- dropped stream cancels underlying stream,
|
||||
- raw generated client remains reachable where needed,
|
||||
- fixture tests pass,
|
||||
- command errors keep raw reply,
|
||||
- API key is redacted from debug output.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake tonic server,
|
||||
- auth tests,
|
||||
- stream order/cancel,
|
||||
- conversion fixtures,
|
||||
- CLI parser/output.
|
||||
|
||||
## Milestone: clients-python
|
||||
|
||||
Goal: implement the async Python client package, test CLI, and tests.
|
||||
|
||||
### Issue: Scaffold Python Package
|
||||
|
||||
Labels: `area:client-python`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `clients/python/pyproject.toml`,
|
||||
- `src/mxgateway`,
|
||||
- generated protobuf modules,
|
||||
- `src/mxgateway_cli`,
|
||||
- `tests`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- package installs editable,
|
||||
- generated stubs import,
|
||||
- `pytest` runs.
|
||||
|
||||
### Issue: Implement Python Async Client, Values, Errors, And CLI
|
||||
|
||||
Labels: `area:client-python`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- async `GatewayClient`,
|
||||
- async `Session`,
|
||||
- auth metadata helper,
|
||||
- TLS/plaintext setup,
|
||||
- async event iterator,
|
||||
- method helpers,
|
||||
- value conversion helpers,
|
||||
- typed exceptions,
|
||||
- `click` or `typer` CLI,
|
||||
- JSON output.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- API key metadata included,
|
||||
- async cancellation cancels stream/call,
|
||||
- raw protobuf replies available,
|
||||
- fixture conversions pass,
|
||||
- secrets redacted.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake async stub tests,
|
||||
- metadata tests,
|
||||
- cancellation tests,
|
||||
- conversion fixtures,
|
||||
- CLI parser/output.
|
||||
|
||||
## Milestone: clients-java
|
||||
|
||||
Goal: implement Java client library, CLI, and tests.
|
||||
|
||||
### Issue: Scaffold Java Gradle Build
|
||||
|
||||
Labels: `area:client-java`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `clients/java/settings.gradle`,
|
||||
- `mxgateway-client` project,
|
||||
- `mxgateway-cli` project,
|
||||
- protobuf/gRPC Gradle generation,
|
||||
- JUnit test setup.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- `gradle test` runs,
|
||||
- generated code compiles,
|
||||
- Java 21 toolchain used.
|
||||
|
||||
### Issue: Implement Java Client, Session, Values, Errors, And CLI
|
||||
|
||||
Labels: `area:client-java`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `MxGatewayClientOptions`,
|
||||
- `MxGatewayClient`,
|
||||
- `MxGatewaySession`,
|
||||
- auth interceptor,
|
||||
- plaintext/TLS channels,
|
||||
- blocking and async event stream options,
|
||||
- method helpers,
|
||||
- value conversion helpers,
|
||||
- typed exceptions,
|
||||
- `picocli` CLI,
|
||||
- JSON output.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- unary and streaming calls carry auth metadata,
|
||||
- deadlines are applied,
|
||||
- stream cancellation works,
|
||||
- raw generated messages are accessible,
|
||||
- fixture tests pass,
|
||||
- CLI redacts secrets.
|
||||
|
||||
Tests:
|
||||
|
||||
- in-process gRPC tests,
|
||||
- auth interceptor,
|
||||
- stream cancellation,
|
||||
- conversion fixtures,
|
||||
- CLI parser/output.
|
||||
|
||||
## Milestone: integration-and-parity
|
||||
|
||||
Goal: prove clients can talk to the gateway consistently.
|
||||
|
||||
### Issue: Cross-Language Smoke Test Matrix
|
||||
|
||||
Labels: `area:tests`, `type:test`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- common smoke script or documented commands,
|
||||
- each client runs open/register/add/advise/stream/close,
|
||||
- JSON output comparison,
|
||||
- optional write test.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- each client has equivalent smoke behavior,
|
||||
- each client skips integration unless `MXGATEWAY_INTEGRATION=1`,
|
||||
- failed smoke output includes endpoint, language, and redacted auth context.
|
||||
|
||||
### Issue: Client Packaging Documentation
|
||||
|
||||
Labels: `area:docs`, `type:docs`, `priority:p2`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- install instructions per client,
|
||||
- generation instructions,
|
||||
- CLI usage examples,
|
||||
- TLS/API key examples,
|
||||
- integration test instructions.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- new developer can build each client from a clean checkout using
|
||||
`docs/toolchain-links.md`,
|
||||
- generated code command is documented for every language.
|
||||
|
||||
@@ -0,0 +1,511 @@
|
||||
# Gateway Implementation Plan
|
||||
|
||||
This plan implements the .NET 10 gateway process first. It covers contracts,
|
||||
configuration, API-key authentication, worker lifecycle, gRPC APIs, event
|
||||
streaming, metrics, dashboard, tests, and operational hooks.
|
||||
|
||||
Primary designs:
|
||||
|
||||
- `docs/gateway-process-design.md`
|
||||
- `docs/gateway-dashboard-design.md`
|
||||
- `docs/design-decisions.md`
|
||||
- `docs/toolchain-links.md`
|
||||
|
||||
## Milestone: gateway-foundation
|
||||
|
||||
Goal: create the solution, shared contracts, configuration model, logging, and
|
||||
test scaffolding that all later work depends on.
|
||||
|
||||
### Issue: Scaffold Gateway Solution And Projects
|
||||
|
||||
Labels: `area:gateway`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- create `src/MxGateway.sln`,
|
||||
- create `src/MxGateway.Contracts`,
|
||||
- create `src/MxGateway.Server`,
|
||||
- create `src/MxGateway.Tests`,
|
||||
- create `src/MxGateway.IntegrationTests`,
|
||||
- target `MxGateway.Server` to `net10.0`,
|
||||
- add shared C# build settings in `Directory.Build.props`,
|
||||
- add baseline tests.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- `dotnet build src/MxGateway.sln` succeeds,
|
||||
- `dotnet test src/MxGateway.sln` succeeds,
|
||||
- gateway project does not reference MXAccess COM.
|
||||
|
||||
### Issue: Define Protobuf Contracts
|
||||
|
||||
Labels: `area:contracts`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `src/MxGateway.Contracts/Protos/mxaccess_gateway.proto`,
|
||||
- `src/MxGateway.Contracts/Protos/mxaccess_worker.proto`,
|
||||
- `MxAccessGateway` service with `OpenSession`, `CloseSession`, `Invoke`, and
|
||||
`StreamEvents`,
|
||||
- `WorkerEnvelope` and worker IPC messages,
|
||||
- `MxValue`, `MxArray`, `MxStatusProxy`, `MxEvent`, and first-slice command
|
||||
payloads,
|
||||
- generated C# code.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- generated code builds,
|
||||
- worker envelopes include protocol version, session id, sequence, and
|
||||
correlation id,
|
||||
- command replies preserve protocol status, HRESULT, return value, out params,
|
||||
and status arrays.
|
||||
|
||||
Tests:
|
||||
|
||||
- protobuf generation smoke,
|
||||
- serialization round-trip for command, reply, event, value, and status.
|
||||
|
||||
### Issue: Add Gateway Configuration And Validation
|
||||
|
||||
Labels: `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- typed options for authentication, worker, sessions, events, dashboard, and
|
||||
protocol,
|
||||
- startup validation,
|
||||
- defaults matching design docs,
|
||||
- redacted effective-configuration model.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- invalid worker path, invalid queue capacity, invalid auth config, and invalid
|
||||
dashboard config fail startup clearly,
|
||||
- redacted config never includes API key pepper or raw secrets.
|
||||
|
||||
Tests:
|
||||
|
||||
- options binding,
|
||||
- validation,
|
||||
- redaction.
|
||||
|
||||
### Issue: Add Structured Logging And Metrics Foundation
|
||||
|
||||
Labels: `area:gateway`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- logging scopes for session id, worker process id, correlation id, command
|
||||
method, and client identity,
|
||||
- counters/gauges/histograms for sessions, workers, commands, events, queues,
|
||||
and faults,
|
||||
- redaction helpers.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- common logs include correlation fields,
|
||||
- API keys and credential-bearing values are not logged,
|
||||
- metrics can feed dashboard snapshots.
|
||||
|
||||
Tests:
|
||||
|
||||
- log redaction,
|
||||
- metric update tests.
|
||||
|
||||
## Milestone: gateway-auth
|
||||
|
||||
Goal: implement API-key authentication backed by SQLite.
|
||||
|
||||
### Issue: Implement SQLite Auth Store And Migrations
|
||||
|
||||
Labels: `area:auth`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- SQLite schema for `schema_version`, `api_keys`, and `api_key_audit`,
|
||||
- idempotent startup migrations,
|
||||
- newer-schema startup block,
|
||||
- key lookup and audit services.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- empty DB initializes,
|
||||
- existing DB migrates,
|
||||
- newer DB version blocks startup,
|
||||
- revoked keys cannot authenticate.
|
||||
|
||||
Tests:
|
||||
|
||||
- temp SQLite migration tests,
|
||||
- key lookup tests,
|
||||
- revoked key tests.
|
||||
|
||||
### Issue: Implement API Key Hashing And Verification
|
||||
|
||||
Labels: `area:auth`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- parse `mxgw_<key-id>_<secret>` format,
|
||||
- HMAC-SHA256 with gateway-local pepper or accepted Argon2id dependency,
|
||||
- constant-time hash comparison,
|
||||
- key id/display name/scopes identity model.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- raw secrets are never stored,
|
||||
- malformed keys fail unauthenticated,
|
||||
- valid keys authenticate,
|
||||
- revoked keys fail.
|
||||
|
||||
Tests:
|
||||
|
||||
- parse tests,
|
||||
- hash verification,
|
||||
- redaction,
|
||||
- scope extraction.
|
||||
|
||||
### Issue: Implement Local API Key Admin CLI
|
||||
|
||||
Labels: `area:auth`, `type:feature`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- local admin CLI or gateway subcommand,
|
||||
- `init-db`,
|
||||
- `create-key`,
|
||||
- `list-keys`,
|
||||
- `revoke-key`,
|
||||
- `rotate-key`,
|
||||
- JSON output option.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- created key can authenticate,
|
||||
- listed keys never show raw secret,
|
||||
- revoked key fails authentication,
|
||||
- raw secret is printed exactly once on create/rotate.
|
||||
|
||||
Tests:
|
||||
|
||||
- CLI parser,
|
||||
- temp DB command tests,
|
||||
- JSON redaction.
|
||||
|
||||
### Issue: Add gRPC Authentication And Scope Authorization
|
||||
|
||||
Labels: `area:auth`, `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- gRPC auth middleware/interceptor,
|
||||
- request identity context,
|
||||
- scope checks for sessions, invoke, secure invoke, events, metadata, and
|
||||
admin actions.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- missing/invalid key returns unauthenticated,
|
||||
- valid key with missing scope returns permission denied,
|
||||
- auth applies to unary and streaming calls.
|
||||
|
||||
Tests:
|
||||
|
||||
- unary auth,
|
||||
- streaming auth,
|
||||
- scope mapping.
|
||||
|
||||
## Milestone: gateway-sessions-ipc
|
||||
|
||||
Goal: create, supervise, and communicate with per-session workers.
|
||||
|
||||
### Issue: Implement Worker Frame Protocol
|
||||
|
||||
Labels: `area:gateway`, `area:contracts`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- little-endian uint32 length-prefixed frame reader/writer,
|
||||
- max message size enforcement,
|
||||
- protobuf envelope validation,
|
||||
- protocol violation errors.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- valid frames round-trip,
|
||||
- partial reads are handled,
|
||||
- oversized frames fail before allocation,
|
||||
- wrong protocol/session id is detected.
|
||||
|
||||
Tests:
|
||||
|
||||
- round-trip,
|
||||
- partial read,
|
||||
- malformed length,
|
||||
- max size,
|
||||
- wrong protocol/session.
|
||||
|
||||
### Issue: Implement Worker Process Launcher
|
||||
|
||||
Labels: `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- worker executable validation,
|
||||
- process launch with session id, pipe name, protocol version,
|
||||
- nonce via environment,
|
||||
- startup timeout handling,
|
||||
- failed-startup cleanup.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- command line contains no secrets,
|
||||
- nonce is not logged,
|
||||
- failed startup kills worker and disposes pipe,
|
||||
- process id is recorded.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake worker success/failure,
|
||||
- timeout kill,
|
||||
- command-line redaction.
|
||||
|
||||
### Issue: Implement Gateway WorkerClient
|
||||
|
||||
Labels: `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- named-pipe server,
|
||||
- `GatewayHello`/`WorkerHello` handshake,
|
||||
- read loop,
|
||||
- write loop,
|
||||
- pending command dictionary,
|
||||
- event channel,
|
||||
- heartbeat tracking,
|
||||
- terminal fault handling.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- worker ready establishes `Ready` state,
|
||||
- command reply completes matching pending command,
|
||||
- worker events enter channel in order,
|
||||
- pipe disconnect faults session.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake worker protocol,
|
||||
- command correlation,
|
||||
- late reply,
|
||||
- pipe disconnect,
|
||||
- heartbeat expiration.
|
||||
|
||||
### Issue: Implement Session Manager And Registry
|
||||
|
||||
Labels: `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- session state machine,
|
||||
- registry keyed by session id,
|
||||
- `OpenSession` orchestration,
|
||||
- `CloseSession` idempotency,
|
||||
- lease hooks,
|
||||
- gateway shutdown cleanup.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- only `Ready` sessions accept commands,
|
||||
- close is idempotent,
|
||||
- faulted sessions reject new commands,
|
||||
- shutdown terminates workers.
|
||||
|
||||
Tests:
|
||||
|
||||
- state transitions,
|
||||
- close idempotency,
|
||||
- open failure cleanup,
|
||||
- shutdown cleanup.
|
||||
|
||||
## Milestone: gateway-grpc-events-dashboard
|
||||
|
||||
Goal: expose the public API, stream events, and provide the dashboard.
|
||||
|
||||
### Issue: Implement Public gRPC Service
|
||||
|
||||
Labels: `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `MxAccessGatewayService`,
|
||||
- `OpenSession`,
|
||||
- `CloseSession`,
|
||||
- `Invoke`,
|
||||
- `StreamEvents`,
|
||||
- request validation,
|
||||
- public-to-worker mappers.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- missing session fails clearly,
|
||||
- method-specific payloads map correctly,
|
||||
- HRESULT/status survives in replies,
|
||||
- transport errors are separate from command replies.
|
||||
|
||||
Tests:
|
||||
|
||||
- service unit tests,
|
||||
- mapper tests,
|
||||
- validation tests,
|
||||
- reply/error mapping.
|
||||
|
||||
### Issue: Implement Event Streaming And Backpressure
|
||||
|
||||
Labels: `area:gateway`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- one active subscriber per session,
|
||||
- second-subscriber rejection,
|
||||
- ordered event streaming,
|
||||
- fail-fast queue overflow,
|
||||
- terminal fault propagation,
|
||||
- event-rate metrics.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- event order preserved,
|
||||
- stream cancellation detaches subscriber,
|
||||
- queue overflow faults session,
|
||||
- `OperationComplete` is not synthesized by gateway.
|
||||
|
||||
Tests:
|
||||
|
||||
- order,
|
||||
- single-subscriber enforcement,
|
||||
- cancellation,
|
||||
- overflow.
|
||||
|
||||
### Issue: Implement Dashboard Snapshot Service
|
||||
|
||||
Labels: `area:dashboard`, `type:feature`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- immutable dashboard snapshot DTOs,
|
||||
- session summaries,
|
||||
- worker summaries,
|
||||
- metric summaries,
|
||||
- fault summaries,
|
||||
- `WatchSnapshotsAsync`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- snapshot reads do not mutate session/worker state,
|
||||
- secrets and credential values are redacted,
|
||||
- subscribers dispose cleanly.
|
||||
|
||||
Tests:
|
||||
|
||||
- projection,
|
||||
- redaction,
|
||||
- subscription disposal,
|
||||
- empty/active/faulted states.
|
||||
|
||||
### Issue: Implement Blazor Server Dashboard
|
||||
|
||||
Labels: `area:dashboard`, `type:feature`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- Blazor Server hosting,
|
||||
- Bootstrap CSS/JS assets,
|
||||
- layout/nav,
|
||||
- home page,
|
||||
- sessions page,
|
||||
- workers page,
|
||||
- events page,
|
||||
- settings page,
|
||||
- real-time refresh.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- Bootstrap/local CSS only,
|
||||
- no MudBlazor or other Blazor UI libraries,
|
||||
- pages update without manual refresh,
|
||||
- dashboard can be disabled by config.
|
||||
|
||||
Tests:
|
||||
|
||||
- snapshot service tests,
|
||||
- component tests if bUnit is added,
|
||||
- disabled-dashboard behavior.
|
||||
|
||||
### Issue: Implement Dashboard Authentication
|
||||
|
||||
Labels: `area:dashboard`, `area:auth`, `type:feature`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `/dashboard/login`,
|
||||
- API-key validation with `admin` scope,
|
||||
- HTTP-only secure cookie,
|
||||
- logout,
|
||||
- anti-forgery protection,
|
||||
- optional explicit anonymous-localhost dev mode defaulting false.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- unauthenticated access is denied/redirected,
|
||||
- non-admin key is denied,
|
||||
- admin key logs in,
|
||||
- cookies use secure settings,
|
||||
- API keys never appear in query strings or logs.
|
||||
|
||||
Tests:
|
||||
|
||||
- auth decisions,
|
||||
- non-admin denial,
|
||||
- cookie properties,
|
||||
- redaction.
|
||||
|
||||
## Milestone: integration-and-parity
|
||||
|
||||
Goal: prove gateway behavior with fake workers before depending on live
|
||||
MXAccess.
|
||||
|
||||
### Issue: Build Fake Worker Test Harness
|
||||
|
||||
Labels: `area:tests`, `area:gateway`, `type:test`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- fake worker executable or in-process transport,
|
||||
- scripted hello/ready/reply/event/fault behavior,
|
||||
- malformed protocol scenarios,
|
||||
- slow/hung worker scenarios.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- gateway tests do not require installed MXAccess,
|
||||
- fake worker simulates startup success/failure,
|
||||
- fake worker emits ordered events and faults.
|
||||
|
||||
### Issue: Gateway End-To-End Smoke With Fake Worker
|
||||
|
||||
Labels: `area:tests`, `area:gateway`, `type:test`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- open session,
|
||||
- invoke `Register`, `AddItem`, `Advise`,
|
||||
- stream one event,
|
||||
- close session,
|
||||
- verify metrics/dashboard snapshot changed.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- smoke passes without live MXAccess,
|
||||
- worker exits,
|
||||
- artifacts stay in temp directories.
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# Implementation Plan Index
|
||||
|
||||
This index defines the implementation order and a Gitea issue/milestone model
|
||||
for tracking the work.
|
||||
|
||||
Repository:
|
||||
|
||||
```text
|
||||
https://gitea.dohertylan.com/dohertj2/mxaccessgw
|
||||
```
|
||||
|
||||
Implementation order:
|
||||
|
||||
1. Gateway process
|
||||
2. MXAccess worker instance
|
||||
3. Client libraries
|
||||
|
||||
Detailed plans:
|
||||
|
||||
- `docs/implementation-plan-gateway.md`
|
||||
- `docs/implementation-plan-mxaccess-worker.md`
|
||||
- `docs/implementation-plan-clients.md`
|
||||
|
||||
## Gitea Milestones
|
||||
|
||||
Recommended milestones:
|
||||
|
||||
1. `gateway-foundation`
|
||||
2. `gateway-auth`
|
||||
3. `gateway-sessions-ipc`
|
||||
4. `gateway-grpc-events-dashboard`
|
||||
5. `mxaccess-worker-foundation`
|
||||
6. `mxaccess-worker-parity-slice`
|
||||
7. `clients-dotnet`
|
||||
8. `clients-go`
|
||||
9. `clients-rust`
|
||||
10. `clients-python`
|
||||
11. `clients-java`
|
||||
12. `integration-and-parity`
|
||||
13. `packaging-and-ops`
|
||||
|
||||
## Gitea Labels
|
||||
|
||||
Recommended labels:
|
||||
|
||||
- `area:contracts`
|
||||
- `area:gateway`
|
||||
- `area:worker`
|
||||
- `area:dashboard`
|
||||
- `area:auth`
|
||||
- `area:client-dotnet`
|
||||
- `area:client-go`
|
||||
- `area:client-rust`
|
||||
- `area:client-python`
|
||||
- `area:client-java`
|
||||
- `area:tests`
|
||||
- `area:docs`
|
||||
- `type:feature`
|
||||
- `type:test`
|
||||
- `type:infra`
|
||||
- `type:docs`
|
||||
- `priority:p0`
|
||||
- `priority:p1`
|
||||
- `priority:p2`
|
||||
- `blocked`
|
||||
|
||||
## Issue Body Template
|
||||
|
||||
```markdown
|
||||
## Context
|
||||
|
||||
## Deliverables
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
## Tests
|
||||
|
||||
## Dependencies
|
||||
```
|
||||
|
||||
## Definition Of Done
|
||||
|
||||
Every implementation issue should meet this baseline:
|
||||
|
||||
- follows the relevant style guide in `docs/style-guides/`,
|
||||
- generated code is reproducible,
|
||||
- secrets are not logged,
|
||||
- unit tests pass,
|
||||
- docs are updated when behavior, commands, or paths change,
|
||||
- live MXAccess verification steps are documented when required.
|
||||
|
||||
## Toolchain
|
||||
|
||||
Use `docs/toolchain-links.md` for installed compiler/runtime paths. If a new
|
||||
terminal cannot find a recently installed tool, refresh PATH:
|
||||
|
||||
```powershell
|
||||
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
|
||||
```
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
# MXAccess Worker Implementation Plan
|
||||
|
||||
This plan implements the .NET Framework 4.8 x86 worker process after the
|
||||
gateway foundation exists. The worker owns MXAccess COM, the dedicated STA
|
||||
thread, message pumping, command dispatch, event sinks, conversion, heartbeat,
|
||||
and shutdown.
|
||||
|
||||
Primary designs:
|
||||
|
||||
- `docs/mxaccess-worker-instance-design.md`
|
||||
- `docs/design-decisions.md`
|
||||
- `docs/toolchain-links.md`
|
||||
- `C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Public-API.md`
|
||||
|
||||
## Milestone: mxaccess-worker-foundation
|
||||
|
||||
Goal: create the worker executable, connect to the gateway pipe, and report
|
||||
ready from a functioning STA runtime.
|
||||
|
||||
### Issue: Scaffold Worker Project
|
||||
|
||||
Labels: `area:worker`, `type:infra`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- create `src/MxGateway.Worker`,
|
||||
- target `.NET Framework 4.8`,
|
||||
- platform target `x86`,
|
||||
- reference generated worker contracts,
|
||||
- reference `ArchestrA.MXAccess.dll`,
|
||||
- create `src/MxGateway.Worker.Tests`,
|
||||
- document MSBuild command from `docs/toolchain-links.md`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- worker builds as x86,
|
||||
- worker tests run,
|
||||
- MXAccess interop reference exists only inside worker boundary,
|
||||
- gateway project does not reference MXAccess.
|
||||
|
||||
Tests:
|
||||
|
||||
- worker build,
|
||||
- worker test project compile.
|
||||
|
||||
### Issue: Implement Worker Bootstrap And Options
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- parse `--session-id`,
|
||||
- parse `--pipe-name`,
|
||||
- parse `--protocol-version`,
|
||||
- read `MXGATEWAY_WORKER_NONCE`,
|
||||
- configure minimal structured logging,
|
||||
- redact nonce and secrets.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- missing required arguments fail fast,
|
||||
- invalid protocol version fails fast,
|
||||
- nonce is never logged,
|
||||
- bootstrap returns structured exit codes.
|
||||
|
||||
Tests:
|
||||
|
||||
- parser tests,
|
||||
- missing/invalid values,
|
||||
- redaction.
|
||||
|
||||
### Issue: Implement Pipe Client And Frame Protocol
|
||||
|
||||
Labels: `area:worker`, `area:contracts`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- connect to named pipe,
|
||||
- frame reader/writer,
|
||||
- envelope validation,
|
||||
- `WorkerHello`,
|
||||
- `GatewayHello` validation,
|
||||
- `WorkerReady`,
|
||||
- `WorkerFault`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- session id, protocol, and nonce are validated before MXAccess creation,
|
||||
- protocol mismatch fails session,
|
||||
- malformed frames fault worker,
|
||||
- all pipe writes go through one writer.
|
||||
|
||||
Tests:
|
||||
|
||||
- frame round-trip,
|
||||
- wrong session/protocol/nonce,
|
||||
- malformed frame,
|
||||
- writer serialization.
|
||||
|
||||
### Issue: Implement STA Runtime And Message Pump
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- dedicated STA thread,
|
||||
- COM initialization on STA,
|
||||
- command queue wake event,
|
||||
- `MsgWaitForMultipleObjectsEx` loop,
|
||||
- `PeekMessage`/`TranslateMessage`/`DispatchMessage`,
|
||||
- last STA activity timestamp,
|
||||
- clean thread shutdown.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- commands execute on STA thread,
|
||||
- pump continues while idle,
|
||||
- shutdown exits thread,
|
||||
- watchdog sees STA activity.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake command executes on STA,
|
||||
- queue wake,
|
||||
- shutdown,
|
||||
- watchdog timestamp.
|
||||
|
||||
### Issue: Create MXAccess COM Object On STA
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- instantiate `ArchestrA.MxAccess.LMXProxyServerClass`,
|
||||
- record CLSID/ProgID/interoperability info,
|
||||
- attach base event handlers,
|
||||
- send `WorkerReady` only after COM creation succeeds,
|
||||
- structured fault on COM creation failure.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- COM creation happens on STA only,
|
||||
- gateway receives ready with worker info,
|
||||
- failure includes HRESULT/exception where available,
|
||||
- raw COM object never crosses threads.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake COM factory tests,
|
||||
- COM creation failure mapping,
|
||||
- worker info mapping.
|
||||
|
||||
Live tests:
|
||||
|
||||
- opt-in live COM creation on installed MXAccess machine.
|
||||
|
||||
## Milestone: mxaccess-worker-parity-slice
|
||||
|
||||
Goal: implement first end-to-end command/event slice through real MXAccess.
|
||||
|
||||
### Issue: Implement STA Command Dispatcher
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `StaCommand` model,
|
||||
- command queue,
|
||||
- one-at-a-time execution,
|
||||
- command reply creation,
|
||||
- cancellation before command starts,
|
||||
- late-reply behavior after gateway timeout/cancel.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- command order is preserved,
|
||||
- exceptions convert to command replies,
|
||||
- current command correlation appears in heartbeat,
|
||||
- shutdown rejects new commands.
|
||||
|
||||
Tests:
|
||||
|
||||
- order,
|
||||
- exception mapping,
|
||||
- cancellation-before-start,
|
||||
- shutdown rejection.
|
||||
|
||||
### Issue: Implement Register And Unregister
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `Register`,
|
||||
- `Unregister`,
|
||||
- server handle tracking,
|
||||
- HRESULT/exception capture.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- returned MXAccess server handle is preserved,
|
||||
- invalid unregister behavior is preserved,
|
||||
- registry state is updated for diagnostics and cleanup only.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake MXAccess register/unregister,
|
||||
- invalid handle mapping,
|
||||
- registry updates.
|
||||
|
||||
Live tests:
|
||||
|
||||
- real `Register`/`Unregister`.
|
||||
|
||||
### Issue: Implement AddItem, AddItem2, RemoveItem
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `AddItem`,
|
||||
- `AddItem2`,
|
||||
- `RemoveItem`,
|
||||
- item handle tracking,
|
||||
- context string preservation,
|
||||
- invalid/cross-server handle behavior preservation.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- returned item handles are not rewritten,
|
||||
- context is passed exactly to MXAccess,
|
||||
- invalid handles preserve HRESULT/status/exception shape.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake item lifecycle,
|
||||
- context mapping,
|
||||
- invalid/cross-server cases.
|
||||
|
||||
Live tests:
|
||||
|
||||
- real `AddItem`,
|
||||
- real `AddItem2("TestInt", "TestChildObject")`.
|
||||
|
||||
### Issue: Implement Advise, UnAdvise, AdviseSupervisory
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- advise command handlers,
|
||||
- advise state tracking,
|
||||
- plain and supervisory methods,
|
||||
- unadvise cleanup.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- calls execute on STA,
|
||||
- advise state is tracked for cleanup,
|
||||
- plain and supervisory methods remain distinct commands.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake advise/unadvise,
|
||||
- cleanup state,
|
||||
- invalid handle mapping.
|
||||
|
||||
Live tests:
|
||||
|
||||
- advise known tag and observe first event where provider state allows.
|
||||
|
||||
### Issue: Implement Event Sink And Event Queue
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- handlers for `OnDataChange`,
|
||||
- handlers for `OnWriteComplete`,
|
||||
- handlers for `OperationComplete`,
|
||||
- handlers for `OnBufferedDataChange`,
|
||||
- monotonic worker event sequence,
|
||||
- bounded outbound event queue,
|
||||
- fail-fast overflow.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- events are enqueued, not pipe-written on STA,
|
||||
- order is preserved,
|
||||
- `OperationComplete` is not synthesized,
|
||||
- buffered events preserve raw metadata if conversion is incomplete,
|
||||
- overflow faults session.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake event conversion,
|
||||
- ordering,
|
||||
- overflow,
|
||||
- no synthetic operation complete.
|
||||
|
||||
Live tests:
|
||||
|
||||
- real `OnDataChange` and `OnWriteComplete` where provider emits them.
|
||||
|
||||
### Issue: Implement Value Conversion
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- scalar `VARIANT` conversion,
|
||||
- SAFEARRAY conversion,
|
||||
- raw fallback metadata,
|
||||
- timestamp conversion,
|
||||
- array rank/dimension metadata where available.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- bool/int/float/double/string/time conversions work,
|
||||
- arrays convert for supported types,
|
||||
- unknown values keep raw metadata,
|
||||
- credential-bearing values are not logged.
|
||||
|
||||
Tests:
|
||||
|
||||
- scalar conversion matrix,
|
||||
- array conversion matrix,
|
||||
- null/empty cases,
|
||||
- raw fallback.
|
||||
|
||||
### Issue: Implement MXSTATUS_PROXY And HRESULT Conversion
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- `MXSTATUS_PROXY[]` conversion,
|
||||
- category/source/detail preservation,
|
||||
- success field preservation,
|
||||
- HRESULT extraction from COM exceptions,
|
||||
- safe diagnostic messages.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- status arrays are not collapsed,
|
||||
- raw fields preserved,
|
||||
- exception HRESULT is captured,
|
||||
- completion-only status bytes are raw unless exact mapping is proven.
|
||||
|
||||
Tests:
|
||||
|
||||
- status struct conversion,
|
||||
- exception/HRESULT mapping,
|
||||
- raw fallback metadata.
|
||||
|
||||
### Issue: Implement Heartbeat And Watchdog
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- periodic heartbeat messages,
|
||||
- last STA activity,
|
||||
- pending command count,
|
||||
- current command correlation id,
|
||||
- event queue depth,
|
||||
- watchdog warnings.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- gateway receives updates,
|
||||
- stuck command is visible in heartbeat,
|
||||
- high queue/stale activity warnings are observable.
|
||||
|
||||
Tests:
|
||||
|
||||
- heartbeat payload,
|
||||
- stale activity,
|
||||
- queue depth.
|
||||
|
||||
### Issue: Implement Graceful Shutdown
|
||||
|
||||
Labels: `area:worker`, `type:feature`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- handle `WorkerShutdown`,
|
||||
- reject new commands,
|
||||
- let current command finish within timeout,
|
||||
- best-effort `UnAdvise`, `RemoveItem`, `Unregister`,
|
||||
- detach event handlers,
|
||||
- release COM object,
|
||||
- exit process.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- cleanup order follows design,
|
||||
- cleanup failures are logged but do not hang shutdown,
|
||||
- gateway can kill after timeout,
|
||||
- worker exits with success on graceful shutdown.
|
||||
|
||||
Tests:
|
||||
|
||||
- fake cleanup order,
|
||||
- cleanup failure,
|
||||
- command in progress,
|
||||
- shutdown timeout.
|
||||
|
||||
## Milestone: integration-and-parity
|
||||
|
||||
Goal: prove gateway plus worker behavior against installed MXAccess.
|
||||
|
||||
### Issue: Worker Live MXAccess Smoke Test
|
||||
|
||||
Labels: `area:worker`, `area:tests`, `type:test`, `priority:p0`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- opt-in live test harness,
|
||||
- open gateway session,
|
||||
- spawn worker,
|
||||
- create MXAccess COM,
|
||||
- `Register`,
|
||||
- `AddItem`,
|
||||
- `Advise`,
|
||||
- wait bounded time for data/status,
|
||||
- `CloseSession`.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- test skips without explicit environment variable,
|
||||
- test cleans up worker even on failure,
|
||||
- logs include enough data for parity debugging.
|
||||
|
||||
### Issue: Parity Fixture Matrix
|
||||
|
||||
Labels: `area:worker`, `area:tests`, `type:test`, `priority:p1`
|
||||
|
||||
Deliverables:
|
||||
|
||||
- fixture list based on `C:\Users\dohertj2\Desktop\mxaccess\captures`,
|
||||
- scenarios for invalid handles, write statuses, secured writes, add-item
|
||||
context, and buffered registration,
|
||||
- comparison format for direct MXAccess vs gateway.
|
||||
|
||||
Acceptance criteria:
|
||||
|
||||
- each public method has planned parity fixture or documented gap,
|
||||
- gateway results preserve HRESULT/status/value/event shape.
|
||||
|
||||
@@ -0,0 +1,636 @@
|
||||
# MXAccess Worker Instance Detailed Design
|
||||
|
||||
## Purpose
|
||||
|
||||
An MXAccess worker instance is the compatibility boundary around one installed
|
||||
MXAccess COM object. It runs as a disposable .NET Framework 4.8 x86 process,
|
||||
owns one dedicated STA thread, pumps Windows/COM messages, executes MXAccess
|
||||
commands on that STA, and forwards MXAccess events back to the gateway.
|
||||
|
||||
The worker's job is not to make MXAccess nicer. Its job is to preserve direct
|
||||
MXAccess behavior while making that behavior available to modern clients through
|
||||
the gateway.
|
||||
|
||||
## Runtime
|
||||
|
||||
- Target runtime: .NET Framework 4.8.
|
||||
- Language: C#.
|
||||
- Platform target: x86 by default.
|
||||
- Process lifetime: one worker per gateway session.
|
||||
- Public network listeners: none.
|
||||
- Gateway IPC: one named pipe with protobuf-framed messages.
|
||||
- COM apartment: one dedicated STA thread.
|
||||
|
||||
Style guides:
|
||||
|
||||
- [C# Style Guide](./style-guides/CSharpStyleGuide.md)
|
||||
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
|
||||
|
||||
## Responsibilities
|
||||
|
||||
The worker owns:
|
||||
|
||||
- connection to the gateway pipe,
|
||||
- protocol hello and readiness reporting,
|
||||
- STA thread creation and teardown,
|
||||
- COM initialization on the STA,
|
||||
- MXAccess COM object creation,
|
||||
- MXAccess event sink wiring,
|
||||
- command dispatch on the STA,
|
||||
- MXAccess handle and advise state tracking,
|
||||
- value/status/HRESULT capture,
|
||||
- conversion to worker protobuf DTOs,
|
||||
- event sequencing,
|
||||
- heartbeat reporting,
|
||||
- graceful shutdown.
|
||||
|
||||
The worker does not own:
|
||||
|
||||
- public gRPC API,
|
||||
- client authentication,
|
||||
- cross-session routing,
|
||||
- worker process supervision,
|
||||
- remote TLS,
|
||||
- policy decisions for other sessions.
|
||||
|
||||
## Process Bootstrap
|
||||
|
||||
Expected command-line arguments:
|
||||
|
||||
```text
|
||||
--session-id <sessionId>
|
||||
--pipe-name <pipeName>
|
||||
--protocol-version <version>
|
||||
```
|
||||
|
||||
Expected protected environment values:
|
||||
|
||||
```text
|
||||
MXGATEWAY_WORKER_NONCE=<random nonce>
|
||||
MXGATEWAY_WORKER_LOG_CONTEXT=<optional context>
|
||||
```
|
||||
|
||||
Startup sequence:
|
||||
|
||||
1. Parse command-line arguments.
|
||||
2. Configure minimal logging.
|
||||
3. Validate required values are present.
|
||||
4. Connect to the gateway named pipe.
|
||||
5. Exchange `WorkerHello` and `GatewayHello`.
|
||||
6. Validate protocol version, session id, and nonce.
|
||||
7. Start the STA runtime.
|
||||
8. Create the MXAccess COM object on the STA.
|
||||
9. Attach MXAccess event handlers on the STA.
|
||||
10. Send `WorkerReady`.
|
||||
11. Start pipe read, pipe write, heartbeat, and shutdown coordination loops.
|
||||
|
||||
If validation fails before MXAccess creation, exit quickly with a non-zero exit
|
||||
code. If MXAccess creation fails, send `WorkerFault` when possible and exit.
|
||||
|
||||
## Internal Components
|
||||
|
||||
```text
|
||||
MxGateway.Worker
|
||||
Program
|
||||
Bootstrap
|
||||
WorkerOptions
|
||||
WorkerHost
|
||||
Ipc
|
||||
PipeClient
|
||||
FrameReader
|
||||
FrameWriter
|
||||
WorkerProtocol
|
||||
Sta
|
||||
StaRuntime
|
||||
StaCommandQueue
|
||||
MessagePump
|
||||
StaWatchdog
|
||||
MxAccess
|
||||
MxAccessSession
|
||||
MxAccessCommandDispatcher
|
||||
MxAccessEventSink
|
||||
MxAccessHandleRegistry
|
||||
Conversion
|
||||
VariantConverter
|
||||
SafeArrayConverter
|
||||
StatusProxyConverter
|
||||
HResultMapper
|
||||
```
|
||||
|
||||
## Threading Model
|
||||
|
||||
```text
|
||||
main thread
|
||||
-> parse args
|
||||
-> configure host
|
||||
-> coordinate shutdown
|
||||
|
||||
pipe reader thread/task
|
||||
-> read WorkerEnvelope frames
|
||||
-> validate protocol
|
||||
-> enqueue commands or control messages
|
||||
|
||||
pipe writer thread/task
|
||||
-> serialize WorkerEnvelope frames
|
||||
-> write replies, events, heartbeats, faults
|
||||
|
||||
STA thread
|
||||
-> CoInitializeEx(APARTMENTTHREADED)
|
||||
-> create MXAccess COM object
|
||||
-> attach event handlers
|
||||
-> pump Windows/COM messages
|
||||
-> execute queued commands
|
||||
-> detach events and release COM on shutdown
|
||||
|
||||
watchdog/heartbeat task
|
||||
-> observe STA responsiveness
|
||||
-> send heartbeat or fault
|
||||
```
|
||||
|
||||
No MXAccess method may execute outside the STA thread. Do not use `Task.Run`
|
||||
around COM calls. Do not let event handlers perform pipe writes.
|
||||
|
||||
## STA Runtime
|
||||
|
||||
The STA runtime is the most important part of the worker.
|
||||
|
||||
Startup:
|
||||
|
||||
1. Create a dedicated `Thread`.
|
||||
2. Set apartment state to `ApartmentState.STA`.
|
||||
3. Start the thread.
|
||||
4. Inside the thread, initialize COM.
|
||||
5. Create the MXAccess COM object.
|
||||
6. Attach event handlers.
|
||||
7. Signal ready to the worker host.
|
||||
8. Enter the message pump.
|
||||
|
||||
Shutdown:
|
||||
|
||||
1. Mark the command queue as completing.
|
||||
2. Drain or reject pending commands according to shutdown mode.
|
||||
3. Optionally issue MXAccess cleanup calls for active handles.
|
||||
4. Detach event handlers.
|
||||
5. Release COM references.
|
||||
6. Uninitialize COM.
|
||||
7. Exit the thread.
|
||||
|
||||
## Message Pump
|
||||
|
||||
The STA must pump Windows messages while also processing queued commands. A
|
||||
blocking queue that prevents message pumping is not acceptable.
|
||||
|
||||
Required loop shape:
|
||||
|
||||
```text
|
||||
while not shutdown:
|
||||
while command queue has work:
|
||||
execute one command on STA
|
||||
|
||||
MsgWaitForMultipleObjectsEx(
|
||||
command_event,
|
||||
timeout,
|
||||
QS_ALLINPUT,
|
||||
MWMO_INPUTAVAILABLE)
|
||||
|
||||
while PeekMessage:
|
||||
TranslateMessage
|
||||
DispatchMessage
|
||||
```
|
||||
|
||||
The command queue should signal a Win32 event or equivalent wait handle so the
|
||||
STA can wake without busy-waiting.
|
||||
|
||||
The loop should update a heartbeat timestamp after:
|
||||
|
||||
- successfully pumping messages,
|
||||
- starting a command,
|
||||
- finishing a command,
|
||||
- processing an MXAccess event.
|
||||
|
||||
## COM Creation
|
||||
|
||||
The MXAccess analysis source at `C:\Users\dohertj2\Desktop\mxaccess` identifies
|
||||
the installed COM target:
|
||||
|
||||
- interop assembly:
|
||||
`C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`
|
||||
- assembly identity:
|
||||
`ArchestrA.MxAccess, Version=3.2.0.0, PublicKeyToken=23106a86e706d0ae`
|
||||
- COM class:
|
||||
`ArchestrA.MxAccess.LMXProxyServerClass`
|
||||
- CLSID:
|
||||
`{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
||||
- ProgID:
|
||||
`LMXProxy.LMXProxyServer.1`
|
||||
- version-independent ProgID:
|
||||
`LMXProxy.LMXProxyServer`
|
||||
- registered server:
|
||||
`C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll`
|
||||
- registry view:
|
||||
`HKCR\Wow6432Node\CLSID\{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC}`
|
||||
- threading model:
|
||||
`Apartment`
|
||||
|
||||
The worker should reference the interop assembly and instantiate
|
||||
`LMXProxyServerClass` on the dedicated STA thread. Keep the ProgID and assembly
|
||||
path configurable for diagnostics, but this COM class is the v1 default.
|
||||
|
||||
Creation rules:
|
||||
|
||||
- Create COM object only on the STA.
|
||||
- Attach event handlers only on the STA.
|
||||
- Keep the COM reference private to the STA runtime.
|
||||
- Never marshal the raw COM object to pipe reader/writer threads.
|
||||
- Capture COM creation HRESULT or exception details.
|
||||
|
||||
If COM creation fails, the worker should send a structured fault with:
|
||||
|
||||
- fault category,
|
||||
- exception type,
|
||||
- HRESULT when available,
|
||||
- COM class or ProgID attempted,
|
||||
- worker process id,
|
||||
- session id.
|
||||
|
||||
## Event Sink
|
||||
|
||||
The worker must subscribe to every public MXAccess event family:
|
||||
|
||||
- `OnDataChange`
|
||||
- `OnWriteComplete`
|
||||
- `OperationComplete`
|
||||
- `OnBufferedDataChange`
|
||||
|
||||
Forward these event families only when the native MXAccess COM object raises
|
||||
them. Do not synthesize `OperationComplete` from write completion or command
|
||||
status. `OnBufferedDataChange` must be represented in the protocol now, but
|
||||
multi-sample payload conversion should remain capture-validated; preserve raw
|
||||
metadata whenever conversion is incomplete.
|
||||
|
||||
Event handling rules:
|
||||
|
||||
- Event handlers are expected to run on the STA.
|
||||
- Assign a monotonic worker event sequence.
|
||||
- Convert event args to `WorkerEvent`.
|
||||
- Include value, quality, timestamp, handles, status arrays, and raw status
|
||||
details when available.
|
||||
- Preserve raw event payload metadata for unsupported buffered or
|
||||
completion-only shapes.
|
||||
- Enqueue to the outbound event queue.
|
||||
- Return quickly to preserve message pumping.
|
||||
|
||||
If event conversion throws, catch it inside the event handler, enqueue a
|
||||
structured `WorkerFault` or diagnostic event, and keep the worker alive only if
|
||||
the fault policy allows it.
|
||||
|
||||
## Command Queue
|
||||
|
||||
The pipe reader converts `WorkerCommand` messages into `StaCommand` entries.
|
||||
|
||||
Each entry should include:
|
||||
|
||||
- correlation id,
|
||||
- method name,
|
||||
- method-specific request payload,
|
||||
- enqueue timestamp,
|
||||
- cancellation marker,
|
||||
- reply completion path.
|
||||
|
||||
The STA command dispatcher:
|
||||
|
||||
1. Dequeues one command.
|
||||
2. Checks whether shutdown has started.
|
||||
3. Calls the matching MXAccess method.
|
||||
4. Captures return values, out parameters, status arrays, and HRESULT.
|
||||
5. Converts results to `WorkerCommandReply`.
|
||||
6. Enqueues the reply to the pipe writer.
|
||||
|
||||
The STA should execute one command at a time. MXAccess command ordering must be
|
||||
preserved for one worker.
|
||||
|
||||
## Command Dispatch Surface
|
||||
|
||||
Phase 1 commands:
|
||||
|
||||
- `Register`
|
||||
- `Unregister`
|
||||
- `AddItem`
|
||||
- `RemoveItem`
|
||||
|
||||
Phase 2 event commands:
|
||||
|
||||
- `Advise`
|
||||
- `UnAdvise`
|
||||
- `AdviseSupervisory`
|
||||
|
||||
Full surface:
|
||||
|
||||
- `AddItem2`
|
||||
- `AddBufferedItem`
|
||||
- `SetBufferedUpdateInterval`
|
||||
- `Suspend`
|
||||
- `Activate`
|
||||
- `Write`
|
||||
- `Write2`
|
||||
- `WriteSecured`
|
||||
- `WriteSecured2`
|
||||
- `AuthenticateUser`
|
||||
- `ArchestrAUserToId`
|
||||
|
||||
Diagnostics:
|
||||
|
||||
- `Ping`
|
||||
- `GetSessionState`
|
||||
- `GetWorkerInfo`
|
||||
- `DrainEvents`
|
||||
- `ShutdownWorker`
|
||||
|
||||
Implement method-specific dispatch instead of a generic string method invoker.
|
||||
Parity tests need stable command-specific request and reply shapes.
|
||||
|
||||
## Handle Registry
|
||||
|
||||
The worker should track MXAccess state for diagnostics and cleanup, while still
|
||||
treating MXAccess as the authority.
|
||||
|
||||
Suggested tracked state:
|
||||
|
||||
- registered server handles,
|
||||
- item handles,
|
||||
- item names and context,
|
||||
- server handle for each item,
|
||||
- advise state,
|
||||
- buffered item state,
|
||||
- authenticated user ids if needed,
|
||||
- last command touching each handle.
|
||||
|
||||
Rules:
|
||||
|
||||
- Do not invent handles.
|
||||
- Do not rewrite handles returned by MXAccess.
|
||||
- Preserve invalid-handle behavior from MXAccess.
|
||||
- Preserve cross-server handle behavior from MXAccess.
|
||||
- Use registry state for cleanup and diagnostics, not semantic correction.
|
||||
|
||||
## Value Conversion
|
||||
|
||||
`VariantConverter` should convert COM values into the protobuf `MxValue` union.
|
||||
|
||||
Supported scalar projections:
|
||||
|
||||
- bool,
|
||||
- int32,
|
||||
- int64,
|
||||
- float,
|
||||
- double,
|
||||
- string,
|
||||
- timestamp,
|
||||
- raw fallback.
|
||||
|
||||
Supported arrays:
|
||||
|
||||
- bool array,
|
||||
- int32 array,
|
||||
- float array,
|
||||
- double array,
|
||||
- string array,
|
||||
- timestamp array,
|
||||
- raw fallback.
|
||||
|
||||
Rules:
|
||||
|
||||
- Preserve null and empty values distinctly when MXAccess exposes a distinction.
|
||||
- Preserve array rank and dimensions when available.
|
||||
- Preserve original variant type metadata.
|
||||
- If conversion is lossy, include the best typed value plus raw diagnostic
|
||||
metadata.
|
||||
- Do not throw away values just because they are awkward.
|
||||
|
||||
Credential-bearing values must not be logged.
|
||||
|
||||
## Status And HRESULT Capture
|
||||
|
||||
`MXSTATUS_PROXY` arrays must be represented explicitly. Do not collapse status
|
||||
arrays into a single success flag.
|
||||
|
||||
For every command reply, capture:
|
||||
|
||||
- protocol success/failure,
|
||||
- method name,
|
||||
- correlation id,
|
||||
- COM HRESULT if available,
|
||||
- thrown exception HRESULT if available,
|
||||
- MXAccess return value if any,
|
||||
- method-specific out parameters,
|
||||
- status array,
|
||||
- diagnostic message safe for logs.
|
||||
|
||||
If a COM call throws, map the exception into a command reply instead of
|
||||
crashing the worker, unless the exception indicates process corruption or the
|
||||
configured policy says to fail the session.
|
||||
|
||||
## Cancellation
|
||||
|
||||
Worker cancellation is cooperative at the queue boundary.
|
||||
|
||||
Rules:
|
||||
|
||||
- If a `WorkerCancel` arrives before a command starts, mark the command
|
||||
canceled and reply or drop according to protocol policy.
|
||||
- If a command is already executing on the STA, do not attempt to abort the COM
|
||||
call.
|
||||
- When the COM call returns after gateway cancellation, send the reply only if
|
||||
the gateway still wants late replies; otherwise log and discard.
|
||||
- Hard cancellation is process kill by the gateway.
|
||||
|
||||
## Outbound Queues
|
||||
|
||||
The worker should use bounded outbound queues for replies, events, heartbeats,
|
||||
and faults.
|
||||
|
||||
Priority order when writing:
|
||||
|
||||
1. faults,
|
||||
2. command replies,
|
||||
3. shutdown acknowledgements,
|
||||
4. heartbeats,
|
||||
5. events.
|
||||
|
||||
Event overflow policy defaults to fail-fast for parity testing. If the event
|
||||
queue fills:
|
||||
|
||||
1. Capture overflow metrics.
|
||||
2. Send `WorkerFault` if possible.
|
||||
3. Stop accepting new commands.
|
||||
4. Let the gateway close or kill the worker.
|
||||
|
||||
Production coalescing may be added later, but it must be explicit and tested.
|
||||
Do not drop or coalesce events in v1.
|
||||
|
||||
## Heartbeat And Watchdog
|
||||
|
||||
The worker heartbeat should prove that:
|
||||
|
||||
- pipe writer is alive,
|
||||
- worker host is alive,
|
||||
- STA has recently pumped or completed work.
|
||||
|
||||
Heartbeat payload should include:
|
||||
|
||||
- worker process id,
|
||||
- session id,
|
||||
- current state,
|
||||
- last STA activity timestamp,
|
||||
- pending command count,
|
||||
- outbound event queue depth,
|
||||
- event sequence,
|
||||
- current command correlation id if any.
|
||||
|
||||
The STA watchdog should warn when:
|
||||
|
||||
- one command exceeds its expected duration,
|
||||
- the STA has not pumped messages within the heartbeat grace period,
|
||||
- event queue depth remains high.
|
||||
|
||||
The worker can report the problem, but the gateway owns the final kill decision.
|
||||
|
||||
## Shutdown
|
||||
|
||||
Graceful shutdown sequence:
|
||||
|
||||
1. Pipe reader receives `WorkerShutdown`.
|
||||
2. Worker host marks shutdown requested.
|
||||
3. Reject new commands.
|
||||
4. Let current STA command finish if within timeout.
|
||||
5. Optionally run MXAccess cleanup:
|
||||
- `UnAdvise`,
|
||||
- `RemoveItem`,
|
||||
- `Unregister`.
|
||||
6. Detach event handlers.
|
||||
7. Release COM object until reference count reaches zero when possible.
|
||||
8. Stop pipe reader and writer.
|
||||
9. Exit process with success code.
|
||||
|
||||
If shutdown wedges, the gateway kills the process. The worker should be written
|
||||
so process kill does not corrupt other sessions.
|
||||
|
||||
## Fault Handling
|
||||
|
||||
Worker fault categories:
|
||||
|
||||
- `InvalidArguments`
|
||||
- `GatewayAuthenticationFailed`
|
||||
- `ProtocolMismatch`
|
||||
- `ProtocolViolation`
|
||||
- `PipeDisconnected`
|
||||
- `MxAccessCreationFailed`
|
||||
- `MxAccessCommandFailed`
|
||||
- `MxAccessEventConversionFailed`
|
||||
- `StaHung`
|
||||
- `QueueOverflow`
|
||||
- `ShutdownTimeout`
|
||||
|
||||
Fault payload should include:
|
||||
|
||||
- category,
|
||||
- session id,
|
||||
- correlation id when command-specific,
|
||||
- command method when command-specific,
|
||||
- HRESULT when available,
|
||||
- exception type when available,
|
||||
- safe diagnostic message.
|
||||
|
||||
Do not include raw credentials or full secured-write values.
|
||||
|
||||
## Security
|
||||
|
||||
The worker should trust only the launching gateway after validating:
|
||||
|
||||
- expected session id,
|
||||
- expected protocol version,
|
||||
- nonce,
|
||||
- pipe identity where available.
|
||||
|
||||
It should not expose any network listener. It should not accept commands from
|
||||
arbitrary local processes.
|
||||
|
||||
Credential-bearing commands must keep credential data out of:
|
||||
|
||||
- command line,
|
||||
- logs,
|
||||
- metrics labels,
|
||||
- exception messages,
|
||||
- crash dumps when avoidable.
|
||||
|
||||
## Observability
|
||||
|
||||
Worker logs should include:
|
||||
|
||||
- startup arguments except secrets,
|
||||
- protocol version,
|
||||
- gateway handshake result,
|
||||
- MXAccess COM creation result,
|
||||
- command start/end with correlation id,
|
||||
- HRESULT/status summary,
|
||||
- event family and sequence,
|
||||
- queue overflow,
|
||||
- STA watchdog warnings,
|
||||
- shutdown path.
|
||||
|
||||
Metrics can be emitted through the gateway or exposed as worker heartbeat
|
||||
fields. The worker does not need its own public metrics endpoint.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Worker tests that do not require installed MXAccess:
|
||||
|
||||
- frame reader/writer,
|
||||
- protocol validation,
|
||||
- command queue ordering,
|
||||
- STA command scheduling with a fake COM object,
|
||||
- message-pump wake behavior where practical,
|
||||
- value conversion,
|
||||
- status conversion,
|
||||
- event conversion from fake event args,
|
||||
- shutdown state transitions,
|
||||
- queue overflow behavior.
|
||||
|
||||
Live MXAccess tests:
|
||||
|
||||
- COM creation on STA,
|
||||
- `Register` and `Unregister`,
|
||||
- `AddItem` and `RemoveItem`,
|
||||
- `Advise` and one `OnDataChange`,
|
||||
- write completion behavior,
|
||||
- secured write behavior,
|
||||
- buffered data-change behavior,
|
||||
- invalid handle behavior.
|
||||
- no synthesized `OperationComplete` when native MXAccess does not raise it.
|
||||
- raw metadata preservation for buffered payloads that cannot yet be fully
|
||||
converted.
|
||||
|
||||
Live tests should be opt-in and clearly marked because they depend on installed
|
||||
MXAccess COM and provider state.
|
||||
|
||||
## Initial Implementation Slice
|
||||
|
||||
The first worker slice should implement:
|
||||
|
||||
1. Argument parsing and pipe connection.
|
||||
2. Protocol hello and nonce validation.
|
||||
3. STA thread startup.
|
||||
4. COM initialization and MXAccess object creation.
|
||||
5. Message pump with command wake event.
|
||||
6. `WorkerReady`.
|
||||
7. Shutdown command.
|
||||
8. `Register`, `AddItem`, and `Advise`.
|
||||
9. Event sink for one `OnDataChange`.
|
||||
10. Basic value/status conversion.
|
||||
11. Event model coverage for `OperationComplete` and `OnBufferedDataChange`
|
||||
without synthesized events.
|
||||
12. Fault reporting.
|
||||
|
||||
This slice proves the worker can preserve the core MXAccess requirements:
|
||||
single-process isolation, STA ownership, message pumping, command execution,
|
||||
and event delivery.
|
||||
@@ -0,0 +1,76 @@
|
||||
# C# Style Guide
|
||||
|
||||
This guide defines C# conventions for the gateway, worker, .NET client, test
|
||||
CLIs, and C# tests.
|
||||
|
||||
## Baseline
|
||||
|
||||
- Use the latest stable C# version supported by the target runtime.
|
||||
- Enable nullable reference types in new projects.
|
||||
- Treat compiler warnings as actionable. Suppress only with a narrow reason.
|
||||
- Prefer file-scoped namespaces.
|
||||
- Prefer `sealed` classes unless inheritance is required.
|
||||
- Keep public APIs explicit and small. Do not expose generated or transport
|
||||
internals through handwritten abstractions unless raw access is intentional.
|
||||
|
||||
## Source Documentation
|
||||
|
||||
- Maintain the existing documentation style in the file, project, and
|
||||
surrounding component.
|
||||
- Write comments that include business-specific or domain-specific context when
|
||||
that context is available from the code, surrounding docs, or naming.
|
||||
- Prefer XML documentation on public APIs when the behavior is not obvious from
|
||||
the signature.
|
||||
- Avoid comments that restate syntax or control flow.
|
||||
|
||||
## Naming
|
||||
|
||||
- Use PascalCase for public types, methods, properties, events, and enum
|
||||
members.
|
||||
- Use camelCase for local variables, parameters, and private fields.
|
||||
- Prefix private fields with `_` only when that pattern is already established
|
||||
in the project.
|
||||
- Use `Async` suffixes for methods that return `Task`, `Task<T>`,
|
||||
`ValueTask`, or `ValueTask<T>`.
|
||||
- Keep names aligned with MXAccess terms: `MxStatusProxy`, `ServerHandle`,
|
||||
`ItemHandle`, `HResult`, and event family names should match the contract.
|
||||
|
||||
## Async And Cancellation
|
||||
|
||||
- Accept `CancellationToken` on public async methods that perform I/O or wait.
|
||||
- Pass cancellation tokens through to called APIs.
|
||||
- Do not use `Task.Run` to hide blocking COM calls. MXAccess calls belong on
|
||||
the worker STA.
|
||||
- Use `ConfigureAwait(false)` in reusable libraries. It is optional in ASP.NET
|
||||
Core request handling where no synchronization context exists.
|
||||
- Dispose async resources with `await using` when the type implements
|
||||
`IAsyncDisposable`.
|
||||
|
||||
## Errors
|
||||
|
||||
- Preserve protocol, gateway, worker, COM HRESULT, and MXAccess status details.
|
||||
- Use typed exceptions at API boundaries, but prefer result DTOs when callers
|
||||
need method-specific MXAccess output.
|
||||
- Do not log API keys, passwords, secured write values, or full tag values by
|
||||
default.
|
||||
- Include correlation id and session id in diagnostics when available.
|
||||
|
||||
## Protobuf And Generated Code
|
||||
|
||||
- Do not hand-edit generated protobuf or gRPC files.
|
||||
- Keep generated code in a clearly named `Generated` namespace or directory.
|
||||
- Keep mapping code outside gRPC handlers so it can be unit tested.
|
||||
|
||||
## Formatting
|
||||
|
||||
- Run `dotnet format` when a solution or project is available.
|
||||
- Use four spaces for indentation.
|
||||
- Keep one public type per file unless a small nested type is clearer.
|
||||
- Avoid region-heavy files. Split large responsibilities into focused types.
|
||||
|
||||
## Tests
|
||||
|
||||
- Use test names that describe behavior, condition, and result.
|
||||
- Prefer fake workers, fake transports, or fake gRPC services over live
|
||||
MXAccess in unit tests.
|
||||
- Mark live MXAccess tests as opt-in integration tests.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Go Style Guide
|
||||
|
||||
This guide defines Go conventions for the MXAccess Gateway Go client module,
|
||||
test CLI, and tests.
|
||||
|
||||
## Baseline
|
||||
|
||||
- Use idiomatic Go and keep package APIs small.
|
||||
- Run `gofmt` on every changed Go file.
|
||||
- Run `go vet` for non-trivial changes when the module is available.
|
||||
- Keep generated protobuf code under `internal/generated` unless the public API
|
||||
intentionally exposes it.
|
||||
|
||||
## Source Documentation
|
||||
|
||||
- Maintain the existing documentation style in the file, package, and
|
||||
surrounding component.
|
||||
- Write comments that include business-specific or domain-specific context when
|
||||
that context is available from the code, surrounding docs, or naming.
|
||||
- Document exported names when they are part of the public client API.
|
||||
- Avoid comments that restate syntax or control flow.
|
||||
|
||||
## Packages
|
||||
|
||||
- Use short, lowercase package names without underscores.
|
||||
- Keep the reusable library separate from CLI code.
|
||||
- Keep generated code separate from handwritten wrappers.
|
||||
- Prefer internal packages for implementation details that callers should not
|
||||
import.
|
||||
|
||||
## Naming
|
||||
|
||||
- Use exported names only for public API.
|
||||
- Use initialisms consistently: `APIKey`, `TLS`, `HTTP`, `ID`.
|
||||
- Keep MXAccess terms explicit: `ServerHandle`, `ItemHandle`, `MxStatusProxy`,
|
||||
and `HResult`.
|
||||
- Avoid generic helper names such as `Do` or `Process` for command-specific
|
||||
MXAccess behavior.
|
||||
|
||||
## Context And Cancellation
|
||||
|
||||
- Accept `context.Context` as the first parameter for operations that can block.
|
||||
- Do not store contexts in structs.
|
||||
- Respect context cancellation, but document that canceling a client call does
|
||||
not abort an in-flight worker COM call.
|
||||
- Close streams and connections deterministically.
|
||||
|
||||
## Errors
|
||||
|
||||
- Return errors instead of panicking.
|
||||
- Wrap errors with useful context using `%w`.
|
||||
- Support `errors.Is` and `errors.As` for typed gateway, command, and MXAccess
|
||||
errors.
|
||||
- Preserve raw command replies on command errors when available.
|
||||
- Redact API keys and credential-bearing values in error messages.
|
||||
|
||||
## Concurrency
|
||||
|
||||
- Avoid unbounded goroutines and unbounded channels.
|
||||
- Close channels exactly once from the sending side.
|
||||
- Propagate stream errors through explicit result types such as `EventResult`.
|
||||
- Use the race detector for concurrency-heavy changes when practical.
|
||||
|
||||
## Tests
|
||||
|
||||
- Use table-driven tests for conversion and error mapping.
|
||||
- Use `bufconn` or fake generated clients for unit tests.
|
||||
- Keep integration tests behind `MXGATEWAY_INTEGRATION=1` or build tags.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Java Style Guide
|
||||
|
||||
This guide defines Java conventions for the MXAccess Gateway Java client
|
||||
library, CLI, and tests.
|
||||
|
||||
## Baseline
|
||||
|
||||
- Target the Java version defined by the client build, with Java 21 preferred.
|
||||
- Use Gradle unless the repository standardizes on Maven.
|
||||
- Apply a formatter such as Spotless or Google Java Format when configured.
|
||||
- Keep generated protobuf code separate from handwritten wrappers.
|
||||
|
||||
## Source Documentation
|
||||
|
||||
- Maintain the existing documentation style in the file, package, and
|
||||
surrounding component.
|
||||
- Write comments that include business-specific or domain-specific context when
|
||||
that context is available from the code, surrounding docs, or naming.
|
||||
- Use Javadoc for public APIs when behavior, parity constraints, or security
|
||||
requirements are not obvious from the signature.
|
||||
- Avoid comments that restate syntax or control flow.
|
||||
|
||||
## Packages
|
||||
|
||||
- Use lowercase package names under `com.dohertylan.mxgateway`.
|
||||
- Keep client library code separate from CLI code.
|
||||
- Keep generated protobuf classes in a generated package.
|
||||
- Do not expose implementation-only transport helpers as public API.
|
||||
|
||||
## Naming
|
||||
|
||||
- Use `PascalCase` for classes, records, interfaces, and enums.
|
||||
- Use `camelCase` for methods, fields, parameters, and local variables.
|
||||
- Use `UPPER_SNAKE_CASE` for constants.
|
||||
- Use MXAccess terms consistently: `serverHandle`, `itemHandle`,
|
||||
`mxStatusProxy`, and `hResult`.
|
||||
|
||||
## API Design
|
||||
|
||||
- Prefer immutable options objects with builders for public configuration.
|
||||
- Implement `AutoCloseable` for clients and sessions that own resources.
|
||||
- Provide async methods with `CompletableFuture` where useful, but keep a
|
||||
blocking API for simple CLI workflows.
|
||||
- Expose raw generated protobuf messages where parity tests need them.
|
||||
|
||||
## Errors
|
||||
|
||||
- Use typed exceptions for gateway, authentication, authorization, session,
|
||||
worker, command, and MXAccess failures.
|
||||
- Preserve raw command replies in command exceptions when available.
|
||||
- Redact API keys, passwords, and secured write values in `toString`, logs, and
|
||||
CLI output.
|
||||
|
||||
## Streaming
|
||||
|
||||
- Cancel gRPC calls explicitly when callers stop consuming streams.
|
||||
- Do not reorder, coalesce, or drop events in client code.
|
||||
- Avoid unbounded queues in async stream helpers.
|
||||
|
||||
## Tests
|
||||
|
||||
- Use JUnit 5.
|
||||
- Use in-process gRPC servers for unit tests.
|
||||
- Keep live gateway tests behind `MXGATEWAY_INTEGRATION=1` and JUnit
|
||||
assumptions.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Protobuf Style Guide
|
||||
|
||||
This guide defines protobuf conventions for MXAccess Gateway public gRPC and
|
||||
gateway-to-worker IPC contracts.
|
||||
|
||||
## Baseline
|
||||
|
||||
- Use `proto3`.
|
||||
- Keep public gateway contracts and worker IPC contracts in separate `.proto`
|
||||
files.
|
||||
- Treat field numbers as permanent once released.
|
||||
- Do not reuse removed field numbers or enum values. Reserve them.
|
||||
- Keep generated code reproducible from checked-in `.proto` files.
|
||||
|
||||
## Source Documentation
|
||||
|
||||
- Maintain the existing documentation style in the `.proto` file and
|
||||
surrounding contract docs.
|
||||
- Write comments that include business-specific or domain-specific context when
|
||||
that context is available from the contract, surrounding docs, or naming.
|
||||
- Comment fields that carry MXAccess parity details, credential-sensitive data,
|
||||
raw HRESULT/status information, or compatibility constraints.
|
||||
- Avoid comments that restate field types or message nesting.
|
||||
|
||||
## Naming
|
||||
|
||||
- Use `snake_case` for package names, file names, field names, and enum values.
|
||||
- Use `PascalCase` for message, enum, and service names.
|
||||
- Prefix enum values with the enum name or a clear abbreviation to avoid name
|
||||
collisions in generated languages.
|
||||
- Keep MXAccess event family and command names recognizable in enum values.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- Add fields instead of changing field meaning.
|
||||
- Use `oneof` for command payloads, reply payloads, value unions, and event
|
||||
bodies.
|
||||
- Add explicit `UNKNOWN` or `UNSPECIFIED` enum zero values.
|
||||
- Preserve raw HRESULT, MXAccess status, and diagnostic metadata in replies and
|
||||
events.
|
||||
- Use protocol version fields in worker IPC envelopes.
|
||||
|
||||
## Field Rules
|
||||
|
||||
- Do not use required semantics in application code for newly added optional
|
||||
fields unless compatibility behavior is documented.
|
||||
- Prefer explicit wrapper messages for repeated structured values.
|
||||
- Use signed or unsigned integer types based on the actual semantic range.
|
||||
- Represent timestamps with `google.protobuf.Timestamp` unless the source value
|
||||
is not a real timestamp.
|
||||
- Represent durations with `google.protobuf.Duration`.
|
||||
|
||||
## Security
|
||||
|
||||
- Do not define fields that require clients or workers to log secrets.
|
||||
- Mark credential-bearing request fields clearly in comments.
|
||||
- Keep raw values out of diagnostics unless an explicit redacted or opt-in path
|
||||
exists.
|
||||
|
||||
## Generated Code
|
||||
|
||||
- Do not hand-edit generated code.
|
||||
- Keep generation commands documented near the contracts project.
|
||||
- Regenerate all affected language outputs when a contract changes.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Python Style Guide
|
||||
|
||||
This guide defines Python conventions for the MXAccess Gateway Python package,
|
||||
CLI, and tests.
|
||||
|
||||
## Baseline
|
||||
|
||||
- Target modern supported Python versions defined by `pyproject.toml`.
|
||||
- Use `pyproject.toml` for package metadata and tool configuration.
|
||||
- Use type hints for public APIs and non-trivial internal functions.
|
||||
- Run the configured formatter and linter when the package is available.
|
||||
- Keep generated protobuf code separate from handwritten modules.
|
||||
|
||||
## Source Documentation
|
||||
|
||||
- Maintain the existing documentation style in the file, package, and
|
||||
surrounding component.
|
||||
- Write comments that include business-specific or domain-specific context when
|
||||
that context is available from the code, surrounding docs, or naming.
|
||||
- Use docstrings for public classes, functions, and modules when behavior,
|
||||
parity constraints, or security requirements are not obvious from the name and
|
||||
type hints.
|
||||
- Avoid comments that restate syntax or control flow.
|
||||
|
||||
## Package Structure
|
||||
|
||||
- Put library code under `src/mxgateway/`.
|
||||
- Put CLI entry points under `src/mxgateway_cli/`.
|
||||
- Keep generated protobuf modules under a clearly named `generated` package.
|
||||
- Avoid import side effects that open channels, read environment variables, or
|
||||
start background tasks.
|
||||
|
||||
## Naming
|
||||
|
||||
- Use `snake_case` for functions, variables, modules, and methods.
|
||||
- Use `PascalCase` for classes and exceptions.
|
||||
- Use `UPPER_SNAKE_CASE` for constants.
|
||||
- Keep MXAccess names recognizable in public APIs, even when Python wrappers
|
||||
use idiomatic method names.
|
||||
|
||||
## Async
|
||||
|
||||
- Make the client async-first.
|
||||
- Use async context managers for clients and sessions when practical.
|
||||
- Cancel gRPC streams when async iteration is canceled.
|
||||
- Document that canceling a Python task does not abort an in-flight MXAccess
|
||||
COM call inside the worker.
|
||||
|
||||
## Errors
|
||||
|
||||
- Use typed exceptions for transport, authentication, authorization, session,
|
||||
worker, command, and MXAccess failures.
|
||||
- Attach raw protobuf replies to command exceptions when available.
|
||||
- Redact API keys, passwords, and secured write values in exception messages and
|
||||
CLI output.
|
||||
|
||||
## CLI
|
||||
|
||||
- Keep CLI output deterministic for tests.
|
||||
- Support JSON output for automation.
|
||||
- Load API keys from explicit flags or named environment variables. Do not read
|
||||
secrets implicitly during module import.
|
||||
|
||||
## Tests
|
||||
|
||||
- Use `pytest` and `pytest-asyncio`.
|
||||
- Use fake generated stubs or an in-process test gRPC server for unit tests.
|
||||
- Keep live integration tests behind `MXGATEWAY_INTEGRATION=1`.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Rust Style Guide
|
||||
|
||||
This guide defines Rust conventions for the MXAccess Gateway Rust client crate,
|
||||
CLI, and tests.
|
||||
|
||||
## Baseline
|
||||
|
||||
- Run `cargo fmt` on every changed Rust file.
|
||||
- Run `cargo clippy` for non-trivial changes when the crate is available.
|
||||
- Use the current stable Rust toolchain unless the project pins a version.
|
||||
- Keep generated protobuf modules isolated from handwritten API wrappers.
|
||||
|
||||
## Source Documentation
|
||||
|
||||
- Maintain the existing documentation style in the file, crate, and surrounding
|
||||
component.
|
||||
- Write comments that include business-specific or domain-specific context when
|
||||
that context is available from the code, surrounding docs, or naming.
|
||||
- Use `///` documentation for public APIs when behavior, parity constraints, or
|
||||
security requirements are not obvious from the type signature.
|
||||
- Avoid comments that restate syntax or control flow.
|
||||
|
||||
## Crate Structure
|
||||
|
||||
- Keep the reusable client library separate from the CLI binary.
|
||||
- Use small modules for `client`, `session`, `options`, `auth`, `value`, and
|
||||
`error`.
|
||||
- Re-export public types intentionally from `lib.rs`.
|
||||
- Keep generated modules private unless raw protobuf access is part of the API.
|
||||
|
||||
## Naming
|
||||
|
||||
- Use `snake_case` for functions, variables, modules, and fields.
|
||||
- Use `UpperCamelCase` for types, traits, and enum variants.
|
||||
- Use `SCREAMING_SNAKE_CASE` for constants.
|
||||
- Keep protocol names aligned with the protobuf contract where exactness
|
||||
matters.
|
||||
|
||||
## Async And Ownership
|
||||
|
||||
- Use `async` APIs with `tokio` for network operations.
|
||||
- Prefer explicit `close` methods for sessions. Do not rely on `Drop` for async
|
||||
cleanup.
|
||||
- Avoid unbounded channels and background tasks without a shutdown path.
|
||||
- Use borrowed parameters such as `&str` where ownership is not needed.
|
||||
|
||||
## Errors
|
||||
|
||||
- Use `thiserror` for library error enums.
|
||||
- Preserve `tonic::Status`, transport errors, command replies, HRESULTs, and
|
||||
MXAccess status details.
|
||||
- Redact API keys and secured values in `Debug`, `Display`, and tracing output.
|
||||
- Avoid string-only errors for public API failures.
|
||||
|
||||
## Security
|
||||
|
||||
- Use a secret wrapper for API keys when adding a dependency is acceptable.
|
||||
- Do not derive `Debug` on types that contain unredacted secrets.
|
||||
- Prefer explicit redacted display implementations for options and errors.
|
||||
|
||||
## Tests
|
||||
|
||||
- Use `#[tokio::test]` for async tests.
|
||||
- Use fake `tonic` services or trait-backed clients for unit tests.
|
||||
- Keep live gateway tests behind `MXGATEWAY_INTEGRATION=1`.
|
||||
@@ -0,0 +1,172 @@
|
||||
# Toolchain Links
|
||||
|
||||
This machine has the project toolchain installed for gateway, worker, dashboard,
|
||||
protobuf/gRPC contracts, and the planned .NET, Go, Rust, Python, and Java
|
||||
clients.
|
||||
|
||||
If a new terminal cannot find a recently installed tool, refresh PATH in
|
||||
PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [Environment]::GetEnvironmentVariable('Path','User')
|
||||
```
|
||||
|
||||
## Core Tools
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| Git | 2.53.0.windows.2 | `C:\Program Files\Git\cmd\git.exe` |
|
||||
| winget | 1.28.240 | `C:\Users\dohertj2\AppData\Local\Microsoft\WindowsApps\winget.exe` |
|
||||
| SQLCMD | 14.0.1000.169 NT | `C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\SQLCMD.EXE` |
|
||||
| SQLite CLI | 3.53.0 | `C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Links\sqlite3.exe` |
|
||||
| grpcurl | 1.9.3 | `C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Links\grpcurl.exe` |
|
||||
|
||||
## .NET And Windows Build Tools
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| .NET SDK | 10.0.201 | `C:\Program Files\dotnet\dotnet.exe` |
|
||||
| .NET runtime | 10.0.5 | `C:\Program Files\dotnet\shared\Microsoft.NETCore.App` |
|
||||
| ASP.NET Core runtime | 10.0.5 | `C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App` |
|
||||
| Windows Desktop runtime | 10.0.5 | `C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App` |
|
||||
| MSBuild | 17.14.40.60911 | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe` |
|
||||
| Visual Studio Build Tools | 2022 BuildTools | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools` |
|
||||
| VC tools | 14.44.35207 | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207` |
|
||||
| C compiler x64 | 14.44.35207 | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\cl.exe` |
|
||||
| Linker x64 | 14.44.35207 | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\link.exe` |
|
||||
| C compiler x86 | 14.44.35207 | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x86\cl.exe` |
|
||||
| Linker x86 | 14.44.35207 | `C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x86\link.exe` |
|
||||
| LibMan CLI | 3.0.71 | `C:\Users\dohertj2\.dotnet\tools\libman.exe` |
|
||||
|
||||
Reference assemblies:
|
||||
|
||||
```text
|
||||
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8
|
||||
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1
|
||||
```
|
||||
|
||||
Environment:
|
||||
|
||||
```text
|
||||
DOTNET_ROOT=C:\Program Files\dotnet
|
||||
```
|
||||
|
||||
Use `dotnet build` for SDK-style projects. Use the full MSBuild path above when
|
||||
a .NET Framework or COM interop build needs classic Visual Studio MSBuild.
|
||||
|
||||
## Go
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| Go | 1.26.2 windows/amd64 | `C:\Program Files\Go\bin\go.exe` |
|
||||
| protoc-gen-go | latest installed by `go install` | `C:\Users\dohertj2\go\bin\protoc-gen-go.exe` |
|
||||
| protoc-gen-go-grpc | latest installed by `go install` | `C:\Users\dohertj2\go\bin\protoc-gen-go-grpc.exe` |
|
||||
|
||||
Environment:
|
||||
|
||||
```text
|
||||
GOROOT=C:\Program Files\Go
|
||||
GOPATH=C:\Users\dohertj2\go
|
||||
Go plugin bin=C:\Users\dohertj2\go\bin
|
||||
```
|
||||
|
||||
Installed plugin commands:
|
||||
|
||||
```powershell
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
```
|
||||
|
||||
## Rust
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| rustup | 1.29.0 | `C:\Users\dohertj2\.cargo\bin\rustup.exe` |
|
||||
| rustc | 1.95.0 | `C:\Users\dohertj2\.cargo\bin\rustc.exe` |
|
||||
| cargo | 1.95.0 | `C:\Users\dohertj2\.cargo\bin\cargo.exe` |
|
||||
|
||||
Rust uses the MSVC toolchain installed with Visual Studio Build Tools. The
|
||||
default Rust linker smoke test has passed.
|
||||
|
||||
User cargo bin:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\.cargo\bin
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| Python | 3.12.10 | `C:\Users\dohertj2\AppData\Local\Programs\Python\Python312\python.exe` |
|
||||
| Python launcher | installed | `C:\Users\dohertj2\AppData\Local\Programs\Python\Launcher\py.exe` |
|
||||
| pip | 26.0.1 | Python 3.12 site packages |
|
||||
|
||||
Installed Python packages:
|
||||
|
||||
```text
|
||||
grpcio==1.80.0
|
||||
grpcio-tools==1.80.0
|
||||
protobuf==6.33.6
|
||||
pytest==9.0.3
|
||||
pytest-asyncio==1.3.0
|
||||
click==8.3.3
|
||||
typer==0.25.0
|
||||
```
|
||||
|
||||
## Java
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| Java runtime | Temurin 21.0.10+7 LTS | `C:\Program Files\Eclipse Adoptium\jdk-21.0.10.7-hotspot\bin\java.exe` |
|
||||
| Java compiler | Temurin 21.0.10+7 LTS | `C:\Program Files\Eclipse Adoptium\jdk-21.0.10.7-hotspot\bin\javac.exe` |
|
||||
| Gradle | 9.4.1 | `C:\Tools\gradle-9.4.1\bin\gradle.bat` |
|
||||
| Maven | 3.9.15 | `C:\Tools\apache-maven-3.9.15\bin\mvn.cmd` |
|
||||
|
||||
Environment:
|
||||
|
||||
```text
|
||||
JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-21.0.10.7-hotspot
|
||||
GRADLE_HOME=C:\Tools\gradle-9.4.1
|
||||
MAVEN_HOME=C:\Tools\apache-maven-3.9.15
|
||||
```
|
||||
|
||||
## Protobuf And gRPC
|
||||
|
||||
| Tool | Version | Path |
|
||||
| --- | --- | --- |
|
||||
| protoc | 34.1 | `C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Packages\Google.Protobuf_Microsoft.Winget.Source_8wekyb3d8bbwe\bin\protoc.exe` |
|
||||
| Buf | 1.68.4 | `C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Links\buf.exe` |
|
||||
| grpcurl | 1.9.3 | `C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Links\grpcurl.exe` |
|
||||
|
||||
Generated code should be reproducible from the shared `.proto` files. Do not
|
||||
hand-edit generated protobuf or gRPC code.
|
||||
|
||||
## Project-Specific External References
|
||||
|
||||
MXAccess analysis:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\mxaccess
|
||||
C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Public-API.md
|
||||
C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Reverse-Engineering.md
|
||||
```
|
||||
|
||||
Galaxy Repository SQL notes:
|
||||
|
||||
```text
|
||||
C:\Users\dohertj2\Desktop\lmxopcua\gr
|
||||
C:\Users\dohertj2\Desktop\lmxopcua\gr\connectioninfo.md
|
||||
C:\Users\dohertj2\Desktop\lmxopcua\gr\queries
|
||||
```
|
||||
|
||||
## Smoke Checks Performed
|
||||
|
||||
These checks passed after installation:
|
||||
|
||||
- `dotnet build` of a temporary `net48` class library.
|
||||
- `cargo build` of a temporary Rust binary using the MSVC linker.
|
||||
- `go build` of a temporary Go module.
|
||||
- `javac` compile of a temporary Java class.
|
||||
- Python imports for `grpc`, `grpc_tools`, and `pytest`.
|
||||
|
||||
+55
-10
@@ -40,6 +40,37 @@ The worker does not host gRPC. The gateway talks to workers through a small
|
||||
local IPC protocol. Named pipes with protobuf-framed messages are the default
|
||||
transport.
|
||||
|
||||
Detailed follow-up docs:
|
||||
|
||||
- `docs/gateway-process-design.md` covers the .NET 10 gateway process,
|
||||
session manager, worker supervision, gRPC API, event streaming, fault model,
|
||||
security, observability, and test strategy.
|
||||
- `docs/mxaccess-worker-instance-design.md` covers each .NET Framework 4.8 x86
|
||||
MXAccess worker instance, including STA ownership, message pumping, COM
|
||||
lifetime, command dispatch, event sinks, conversion, and shutdown.
|
||||
- `docs/design-decisions.md` records current v1 choices, including API-key
|
||||
authentication in gateway-owned SQLite and the concrete installed MXAccess
|
||||
COM class details from `C:\Users\dohertj2\Desktop\mxaccess`.
|
||||
- `docs/gateway-dashboard-design.md` covers the Blazor Server and Bootstrap
|
||||
dashboard for live gateway/session/worker status.
|
||||
- `docs/client-libraries-design.md` covers shared design requirements for
|
||||
official gRPC client libraries, test CLIs, and tests for .NET C#, Go, Rust,
|
||||
Python, and Java.
|
||||
- `docs/implementation-plan-index.md` links the detailed implementation plans
|
||||
and recommended Gitea milestones/issues.
|
||||
|
||||
Implementation style guides:
|
||||
|
||||
- `StyleGuide.md` covers project documentation.
|
||||
- `docs/style-guides/CSharpStyleGuide.md` covers gateway, worker, .NET client,
|
||||
and C# tests.
|
||||
- `docs/style-guides/ProtobufStyleGuide.md` covers public gRPC and worker IPC
|
||||
contracts.
|
||||
- `docs/style-guides/GoStyleGuide.md` covers the Go client.
|
||||
- `docs/style-guides/RustStyleGuide.md` covers the Rust client.
|
||||
- `docs/style-guides/PythonStyleGuide.md` covers the Python client.
|
||||
- `docs/style-guides/JavaStyleGuide.md` covers the Java client.
|
||||
|
||||
## Process Split
|
||||
|
||||
### Gateway Process
|
||||
@@ -866,16 +897,30 @@ backend = mxaccess-worker
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Exact installed MXAccess COM ProgID/class used by production should be pinned
|
||||
from the existing trace harness.
|
||||
- Whether one gRPC client connection maps to one session or whether sessions can
|
||||
survive client reconnects.
|
||||
- Whether event streams can have multiple subscribers per session.
|
||||
- Required authentication model for remote clients.
|
||||
- Whether worker process identity should be the gateway identity or a restricted
|
||||
service account.
|
||||
- Maximum supported event rate before coalescing is required.
|
||||
- Whether command batching is needed for high-volume tag registration.
|
||||
Current v1 decisions are recorded in `docs/design-decisions.md`.
|
||||
|
||||
Resolved for v1:
|
||||
|
||||
- MXAccess COM target is `ArchestrA.MxAccess.LMXProxyServerClass` /
|
||||
`LMXProxy.LMXProxyServer.1` from the installed 32-bit `LmxProxy.dll`.
|
||||
- One `OpenSession` maps to one worker process; no reconnectable sessions.
|
||||
- One active event subscriber per session.
|
||||
- API key authentication with hashed keys in gateway-owned SQLite.
|
||||
- Basic Blazor Server dashboard with Bootstrap CSS/JS and real-time updates.
|
||||
- Workers run as the gateway service identity.
|
||||
- Event backpressure is fail-fast with bounded queues.
|
||||
- No public command batching.
|
||||
- `OperationComplete` is forwarded only when native MXAccess raises it.
|
||||
- `OnBufferedDataChange` is modeled now; multi-sample payload conversion remains
|
||||
capture-validated work.
|
||||
|
||||
Post-v1 revisit items:
|
||||
|
||||
- production event-rate target and optional coalescing,
|
||||
- reconnectable sessions,
|
||||
- multi-subscriber event fan-out,
|
||||
- restricted worker process identity,
|
||||
- command batching for high-volume setup.
|
||||
|
||||
## Recommended Next Step
|
||||
|
||||
|
||||
Reference in New Issue
Block a user