Align docs with StyleGuide and add CLAUDE.md

- Rename 16 kebab-case docs to PascalCase per StyleGuide
- Move per-language client design docs from docs/ to clients/<lang>/
  alongside their READMEs
- Add ## Related Documentation sections to 15 docs that lacked one
- Fix sentence-case violations in H3 headings (StyleGuide rule)
- Update cross-references in gateway.md, client READMEs, scripts,
  and generate-proto.ps1 helpers to follow the new paths
- Add CLAUDE.md with build/test commands, the source-update
  verification matrix, the parity-first contract, and pointers
  to MXAccess and Galaxy Repository analysis sources

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-30 10:19:22 -04:00
parent 133c83029b
commit 51a9dadf62
45 changed files with 522 additions and 134 deletions
+125
View File
@@ -0,0 +1,125 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
`mxaccessgw` is the MXAccess Gateway: a gRPC service that gives modern (.NET, Go, Rust, Python, Java) clients full MXAccess parity without forcing them to load 32-bit MXAccess COM, run x86, or own an STA message pump.
The architecture is a two-process design — read `gateway.md` before making structural changes:
- **Gateway** (`src/MxGateway.Server`, .NET 10, x64): ASP.NET Core gRPC server. Owns the public API, sessions, auth, the Blazor dashboard, and the Galaxy Repository SQL browse RPCs. **Never instantiates MXAccess COM directly.**
- **Worker** (`src/MxGateway.Worker`, .NET Framework 4.8, **x86**): one process per session. Owns one MXAccess COM instance on a dedicated STA, pumps Windows messages, and converts COM events to protobuf.
- **IPC**: gateway↔worker uses one bidirectional named pipe per worker (`mxaccess-gateway-{gatewayPid}-{sessionId}`) with length-prefixed `WorkerEnvelope` protobuf frames. Gateway hosts the pipe server and launches the worker. **gRPC is not used inside the worker** — .NET Framework 4.8 doesn't have a first-class gRPC stack.
- **Contracts** (`src/MxGateway.Contracts`): multi-targets `net10.0;net48` and owns the `.proto` files (`mxaccess_gateway.proto`, `mxaccess_worker.proto`, `galaxy_repository.proto`). All other projects consume the generated types from here. Do not hand-edit anything under `Generated/`.
The worker must do all MXAccess COM calls on its dedicated STA thread, and the STA loop must pump Windows messages (`MsgWaitForMultipleObjectsEx` + `PeekMessage`/`DispatchMessage`) so MXAccess events deliver. A plain blocking queue on an STA is not enough.
## Build, Test, Run
```powershell
# Full solution build (gateway, worker, contracts, tests)
dotnet build src/MxGateway.sln
# Worker must be built x86 — the gateway looks for MxGateway.Worker.exe under bin\x86
dotnet build src/MxGateway.Worker/MxGateway.Worker.csproj -p:Platform=x86
# Gateway tests (no MXAccess required — uses FakeWorkerHarness)
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj
dotnet test src/MxGateway.Worker.Tests/MxGateway.Worker.Tests.csproj -p:Platform=x86
# Run gateway locally (defaults bound under MxGateway:* in src/MxGateway.Server/appsettings.json)
dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj
# API-key admin CLI (same exe, "apikey" subcommand)
dotnet run --project src/MxGateway.Server/MxGateway.Server.csproj -- apikey create --display-name "dev" --scopes session,invoke,event,metadata,admin
```
Single test by name (xUnit `--filter`):
```powershell
dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj --filter FullyQualifiedName~GatewayEndToEndFakeWorkerSmokeTests
```
Live MXAccess integration tests are **opt-in** because they need installed MXAccess COM and live provider state:
```powershell
$env:MXGATEWAY_RUN_LIVE_MXACCESS_TESTS = "1"
dotnet test src/MxGateway.IntegrationTests/MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~WorkerLiveMxAccessSmokeTests
```
Live LDAP tests use `MXGATEWAY_RUN_LIVE_LDAP_TESTS=1`. See `docs/GatewayTesting.md` for the full opt-in matrix and `LiveMxAccessFactAttribute` / `LiveLdapFactAttribute` for the gating logic.
## Clients
Each language client is in `clients/<lang>/` with its own README. They all consume the shared `.proto` files in `src/MxGateway.Contracts/Protos`:
- `clients/dotnet`: `dotnet build clients/dotnet/MxGateway.Client.sln`
- `clients/python`: `python -m pip install -e ".[dev]"; python -m pytest`
- `clients/rust`: `cargo test --workspace; cargo clippy --workspace --all-targets -- -D warnings`
- `clients/java`: `gradle test` (Java 21)
- Go client lives alongside as `mxgw-go` in the cross-language matrix
End-to-end matrix runner (needs running gateway + worker + valid API key):
```powershell
$env:MXGATEWAY_API_KEY = "<api-key>"
powershell -ExecutionPolicy Bypass -File scripts/run-client-e2e-tests.ps1
```
## Repository-Specific Conventions
- **Build properties** (`src/Directory.Build.props`) enforce `Nullable=enable`, `TreatWarningsAsErrors=true`, latest analyzers, and `EnforceCodeStyleInBuild=true`. New warnings break the build — fix them, don't suppress unless the suppression has a narrow reason.
- **Style guides** in `docs/style-guides/` are authoritative. Follow `CSharpStyleGuide.md` for gateway/worker/.NET-client code: file-scoped namespaces, `sealed` by default, `Async` suffix on Task-returning methods, MXAccess-aligned names (`MxStatusProxy`, `ServerHandle`, `ItemHandle`, `HResult`).
- **MXAccess parity is the contract.** Don't "fix" surprising MXAccess behavior (e.g., `WriteSecured` failing before a value-bearing NMX body, distinct `OperationComplete` semantics, invalid-handle exceptions) unless the client explicitly opts into a non-parity mode. The installed MXAccess COM component is the baseline.
- **Don't synthesize events.** The gateway forwards only events the worker emits; it never invents `OperationComplete` from write completion or command replies.
- **One worker per session, one event subscriber per session** (v1). Multi-subscriber fan-out and reconnectable sessions are explicitly out of scope — see `docs/DesignDecisions.md`.
- **Gateway restart does not reattach orphan workers.** The first version terminates orphaned workers on startup; do not design code paths that assume reattachment.
- **No Blazor UI component libraries.** Dashboard uses local Bootstrap CSS/JS only — do not introduce MudBlazor, Radzen, FluentUI, etc.
- **Don't log secrets or full tag values by default.** API keys, passwords, `WriteSecured` payloads, and `AuthenticateUser` credentials must never reach logs. Value logging is opt-in and redacted.
- **Generated code** under `src/MxGateway.Contracts/Generated/`, `clients/*/generated*/`, `clients/python/src/mxgateway/generated/`, etc., is build output. Don't hand-edit. To regenerate, build the contracts project (`dotnet build src/MxGateway.Contracts/MxGateway.Contracts.csproj`) or run the per-client generation step in that client's README.
- **Documentation style** (`StyleGuide.md`): PascalCase filenames, no marketing language, present tense, explain *why* not *what*.
- **Update docs in the same change as the source.** When public APIs, contracts, configuration, build steps, security behavior, event shapes, value conversion, status mapping, or lifecycle rules change, the affected docs (`gateway.md`, `docs/`, client READMEs, design docs) must change in the same commit. Don't leave stale prose describing old behavior.
## Source Update Workflow
When source code changes, build and test the affected component before reporting work done. If the change crosses component boundaries, build each affected component — don't rely on a single top-level build:
| 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, metrics | `dotnet build src/MxGateway.Server` and run affected gateway / fake-worker tests |
| Worker IPC, STA, MXAccess, conversion | `dotnet build src/MxGateway.Worker -p:Platform=x86` and run worker tests |
| .NET client | `dotnet build clients/dotnet/MxGateway.Client.sln` and run its tests |
| Go client | `gofmt`, `go build ./...`, `go test ./...` from `clients/go` |
| Rust client | `cargo fmt`, `cargo check --workspace`, `cargo test --workspace`, `cargo clippy --all-targets -- -D warnings` from `clients/rust` |
| Python client | `python -m pytest` from `clients/python` |
| Java client | `gradle test` from `clients/java` |
| Integration tests | run only when MXAccess COM, provider state, and external services are available; otherwise document why skipped |
## Design Sources To Consult Before Non-Trivial Changes
- `gateway.md` — top-level architecture, command/event surface, IPC envelope, STA thread model, fault handling.
- `glauth.md` — local LDAP server (GLAuth on `localhost:3893`, base DN `dc=lmxopcua,dc=local`) used for dev authn. Pre-provisioned users (`admin/admin123`, `readonly/readonly123`, etc.) and the role→capability mapping live there.
- `docs/DesignDecisions.md` — v1 choices (MXAccess COM target `LMXProxyServerClass` from `C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`, API-key-in-SQLite auth, fail-fast event backpressure, etc.).
- `docs/GatewayProcessDesign.md`, `docs/MxAccessWorkerInstanceDesign.md`, `docs/WorkerFrameProtocol.md`, `docs/WorkerProcessLauncher.md` — detailed component designs.
- `docs/GatewayConfiguration.md` — full `MxGateway:*` options bound by `GatewayOptions` and validated at startup by `GatewayOptionsValidator`.
- `docs/GatewayTesting.md` — fake worker harness, live MXAccess smoke, parity matrix, cross-language smoke matrix.
- `docs/ToolchainLinks.md` — installed compiler/SDK paths on this dev box (.NET 10.0.201, Go 1.26.2, Rust 1.95, Python 3.12.10, Temurin 21, protoc 34.1, etc.).
External analysis sources referenced by design docs:
- `C:\Users\dohertj2\Desktop\mxaccess` — MXAccess analysis project. Key files: `docs/MXAccess-Public-API.md` (COM class, ProgID, CLSID, method list, event signatures, `MxDataType`, `MxStatus`, `MXSTATUS_PROXY`), `docs/MXAccess-Reverse-Engineering.md` (installed runtime path, x86 COM constraints), `docs/Current-Sprint-State.md` (parity gaps), `src/MxTraceHarness/` (x86 harness using the real COM interop), `captures/` and `analysis/` (observed native behavior).
- `C:\Users\dohertj2\Desktop\lmxopcua\gr` — Galaxy Repository (`ZB` SQL DB) notes. Key files: `connectioninfo.md`, `layout.md`, `schema.md`, `queries/hierarchy.sql`, `queries/attributes.sql`, `queries/attributes_extended.sql`, `queries/change_detection.sql`. Connection is SQL Server `localhost`, database `ZB`, Windows Auth.
## Authentication
Gateway gRPC clients authenticate with an API key in metadata: `authorization: Bearer mxgw_<key-id>_<secret>`. Keys are stored hashed (with a peppered SHA) in a gateway-owned SQLite DB (default `C:\ProgramData\MxGateway\gateway-auth.db`). Scopes (`session`, `invoke`, `event`, `metadata`, `admin`) gate specific RPCs; missing → `Unauthenticated`, insufficient → `PermissionDenied`. The `apikey` subcommand on the server exe manages keys; see `src/MxGateway.Server/Security/Authentication/`.
Dashboard auth uses the same verifier but exchanges the API key for an HTTP-only secure cookie at `/dashboard/login`. `Dashboard:AllowAnonymousLocalhost` bypasses cookie auth on loopback when explicitly enabled.
## Process / Platform Notes
- Working tree is on Windows (`C:\Users\dohertj2\Desktop\mxaccessgw`). PowerShell is the native shell for tooling commands; bash is fine for git/grep/find.
- The worker reference to `ArchestrA.MXAccess.dll` uses an absolute `HintPath` to `C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll`. The worker only builds where MXAccess is installed (this dev box).
- The repo is not a git repository at the top level — there's no `.git` directory in the working tree.
@@ -6,8 +6,8 @@ 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)
Follow the [C# Style Guide](../../docs/style-guides/CSharpStyleGuide.md) for
handwritten code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md)
for generated contract inputs.
## Projects
@@ -211,3 +211,10 @@ MXGATEWAY_TEST_ITEM=<item>
Integration smoke should open, register, add, advise, stream for bounded time,
and close.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [C# Style Guide](../../docs/style-guides/CSharpStyleGuide.md)
+15 -2
View File
@@ -164,6 +164,19 @@ foreach (GalaxyObject galaxyObject in objects)
}
```
Use `DiscoverHierarchyOptions` to request a server-side slice without pulling
the full Galaxy:
```csharp
IReadOnlyList<GalaxyObject> pumps = await repository.DiscoverHierarchyAsync(
new DiscoverHierarchyOptions
{
RootContainedPath = "Area1/Line3",
TagNameGlob = "Pump_*",
IncludeAttributes = false,
});
```
The CLI exposes the same operations:
```powershell
@@ -230,5 +243,5 @@ dotnet run --project clients/dotnet/MxGateway.Client.Cli -- smoke --endpoint $en
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [.NET Client Detailed Design](../../docs/clients-dotnet-csharp-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [.NET Client Detailed Design](./DotnetClientDesign.md)
@@ -6,8 +6,8 @@ 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
Follow the [Go Style Guide](../../docs/style-guides/GoStyleGuide.md) for handwritten
code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md) for
generated contract inputs.
## Module Layout
@@ -176,3 +176,10 @@ MXGATEWAY_INTEGRATION=1
Integration test should run `OpenSession`, `Register`, `AddItem`, `Advise`,
bounded `StreamEvents`, and `CloseSession`.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Go Style Guide](../../docs/style-guides/GoStyleGuide.md)
+4 -4
View File
@@ -3,7 +3,7 @@
The Go client module contains the generated MXAccess Gateway protobuf bindings,
a small handwritten `mxgateway` package, and the `mxgw-go` test CLI scaffold.
The module uses the shared proto inputs documented in
`../../docs/client-proto-generation.md` so gateway and client contracts stay in
`../../docs/ClientProtoGeneration.md` so gateway and client contracts stay in
sync.
## Layout
@@ -28,7 +28,7 @@ Run generation after the shared `.proto` files or the Go output path changes:
./generate-proto.ps1
```
The script uses the tool paths recorded in `../../docs/toolchain-links.md`.
The script uses the tool paths recorded in `../../docs/ToolchainLinks.md`.
## Build And Test
@@ -209,5 +209,5 @@ go run ./cmd/mxgw-go smoke -endpoint $env:MXGATEWAY_ENDPOINT -plaintext -api-key
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Go Client Detailed Design](../../docs/clients-golang-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Go Client Detailed Design](./GoClientDesign.md)
+2 -2
View File
@@ -9,13 +9,13 @@ $protoc = 'C:\Users\dohertj2\AppData\Local\Microsoft\WinGet\Packages\Google.Prot
$goPluginPath = 'C:\Users\dohertj2\go\bin'
if (-not (Test-Path $protoc)) {
throw "protoc was not found at $protoc. See docs/toolchain-links.md."
throw "protoc was not found at $protoc. See docs/ToolchainLinks.md."
}
foreach ($pluginName in @('protoc-gen-go.exe', 'protoc-gen-go-grpc.exe')) {
$pluginPath = Join-Path $goPluginPath $pluginName
if (-not (Test-Path $pluginPath)) {
throw "$pluginName was not found at $pluginPath. See docs/toolchain-links.md."
throw "$pluginName was not found at $pluginPath. See docs/ToolchainLinks.md."
}
}
@@ -5,8 +5,8 @@
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
Follow the [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md) for handwritten
code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md) for
generated contract inputs.
## Build Layout
@@ -210,3 +210,10 @@ The `mxgateway-client` project generates the gateway and worker protobuf/gRPC
bindings into `src/main/generated`, compiles the generated contracts, and runs
JUnit 5 tests. The `mxgateway-cli` project builds a Picocli-based `mxgw-java`
entry point for later command implementation.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
+2 -2
View File
@@ -223,6 +223,6 @@ gradle :mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --pla
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Java Client Detailed Design](../../docs/clients-java-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Java Client Detailed Design](./JavaClientDesign.md)
- [Java Style Guide](../../docs/style-guides/JavaStyleGuide.md)
@@ -6,8 +6,8 @@ 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)
Follow the [Python Style Guide](../../docs/style-guides/PythonStyleGuide.md) for
handwritten code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md)
for generated contract inputs.
## Package Layout
@@ -195,3 +195,10 @@ mxaccess-gateway-client
Generated protobuf code should be regenerated through a documented command, not
edited by hand.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Python Style Guide](../../docs/style-guides/PythonStyleGuide.md)
+4 -4
View File
@@ -3,7 +3,7 @@
The Python client package contains generated MXAccess Gateway protobuf
bindings, the async `mxgateway` package, and the `mxgw-py` test CLI. The
package uses the shared proto inputs documented in
`../../docs/client-proto-generation.md` so gateway and client contracts stay in
`../../docs/ClientProtoGeneration.md` so gateway and client contracts stay in
sync.
## Layout
@@ -31,7 +31,7 @@ changes:
```
The script uses the Python tool path recorded in
`../../docs/toolchain-links.md`.
`../../docs/ToolchainLinks.md`.
## Build And Test
@@ -219,5 +219,5 @@ mxgw-py smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGAT
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Python Client Detailed Design](../../docs/clients-python-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Python Client Detailed Design](./PythonClientDesign.md)
+1 -1
View File
@@ -7,7 +7,7 @@ $outputRoot = Join-Path $PSScriptRoot 'src\mxgateway\generated'
$python = 'C:\Users\dohertj2\AppData\Local\Programs\Python\Python312\python.exe'
if (-not (Test-Path $python)) {
throw "Python was not found at $python. See docs/toolchain-links.md."
throw "Python was not found at $python. See docs/ToolchainLinks.md."
}
New-Item -ItemType Directory -Path $outputRoot -Force | Out-Null
+4 -4
View File
@@ -4,7 +4,7 @@ The Rust client workspace contains the MXAccess Gateway client library, a
test CLI, and tests for generated contract wiring plus wrapper behavior. The
library uses
the shared protobuf inputs documented in
`../../docs/client-proto-generation.md` so the Rust bindings compile against
`../../docs/ClientProtoGeneration.md` so the Rust bindings compile against
the same public gateway and worker contracts as the server.
## Layout
@@ -36,7 +36,7 @@ cargo clippy --workspace --all-targets -- -D warnings
```
The build script uses `protoc` from `PATH` or the Windows path recorded in
`../../docs/toolchain-links.md`.
`../../docs/ToolchainLinks.md`.
## Packaging
@@ -184,6 +184,6 @@ cargo run -p mxgw-cli -- smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --
## Related Documentation
- [Client Packaging](../../docs/ClientPackaging.md)
- [Client Proto Generation](../../docs/client-proto-generation.md)
- [Rust Client Detailed Design](../../docs/clients-rust-design.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Rust Client Detailed Design](./RustClientDesign.md)
- [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md)
@@ -5,8 +5,8 @@
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
Follow the [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md) for handwritten
code and the [Protobuf Style Guide](../../docs/style-guides/ProtobufStyleGuide.md) for
generated contract inputs.
## Crate Layout
@@ -187,3 +187,10 @@ 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.
## Related Documentation
- [Client Libraries Detailed Design](../../docs/ClientLibrariesDesign.md)
- [Client Proto Generation](../../docs/ClientProtoGeneration.md)
- [Client Packaging](../../docs/ClientPackaging.md)
- [Rust Style Guide](../../docs/style-guides/RustStyleGuide.md)
+21 -8
View File
@@ -91,12 +91,15 @@ return ApiKeyVerificationResult.Success(new ApiKeyIdentity(
KeyId: storedKey.KeyId,
KeyPrefix: storedKey.KeyPrefix,
DisplayName: storedKey.DisplayName,
Scopes: storedKey.Scopes));
Scopes: storedKey.Scopes,
Constraints: storedKey.Constraints));
```
`ApiKeyVerificationResult` carries either an `ApiKeyIdentity` or a discriminated `ApiKeyVerificationFailure` value. The failure enum distinguishes parse errors, missing pepper, missing or revoked keys, and secret mismatch so the calling middleware can emit precise audit detail without leaking which check failed to the client.
`ApiKeyIdentity` exposes only non-secret fields (`KeyId`, `KeyPrefix`, `DisplayName`, `Scopes`) and is the type downstream authorization code consumes.
`ApiKeyIdentity` exposes only non-secret fields (`KeyId`, `KeyPrefix`,
`DisplayName`, `Scopes`, and `Constraints`) and is the type downstream
authorization code consumes.
## Storage
@@ -131,7 +134,9 @@ public SqliteConnection CreateConnection()
`SqliteAuthSchema` declares table names and the current schema version as constants. Three tables are involved:
- `api_keys` stores `key_id`, `key_prefix`, the `secret_hash` blob, `display_name`, serialized `scopes`, and the `created_utc`, `last_used_utc`, and `revoked_utc` timestamps.
- `api_keys` stores `key_id`, `key_prefix`, the `secret_hash` blob,
`display_name`, serialized `scopes`, optional serialized `constraints`, and
the `created_utc`, `last_used_utc`, and `revoked_utc` timestamps.
- `api_key_audit` is an append-only log keyed by an autoincrement `audit_id` with `key_id`, `event_type`, `remote_address`, `created_utc`, and `details` columns.
- `schema_version` carries a single row whose `version` column is matched against `SqliteAuthSchema.CurrentVersion`.
@@ -150,9 +155,10 @@ public static ApiKeyRecord Read(SqliteDataReader reader)
SecretHash: (byte[])reader["secret_hash"],
DisplayName: reader.GetString(3),
Scopes: ApiKeyScopeSerializer.Deserialize(reader.GetString(4)),
CreatedUtc: DateTimeOffset.Parse(reader.GetString(5), System.Globalization.CultureInfo.InvariantCulture),
LastUsedUtc: ReadNullableDateTimeOffset(reader, 6),
RevokedUtc: ReadNullableDateTimeOffset(reader, 7));
Constraints: ApiKeyConstraintSerializer.Deserialize(reader.IsDBNull(5) ? null : reader.GetString(5)),
CreatedUtc: DateTimeOffset.Parse(reader.GetString(6), System.Globalization.CultureInfo.InvariantCulture),
LastUsedUtc: ReadNullableDateTimeOffset(reader, 7),
RevokedUtc: ReadNullableDateTimeOffset(reader, 8));
}
```
@@ -193,8 +199,8 @@ The supported subcommands match `ApiKeyAdminCommandKind` exactly:
| Subcommand | Required options | Behaviour |
|------------|------------------|-----------|
| `init-db` | none | Runs the migrator and records an audit entry. |
| `create-key` | `--key-id`, `--display-name` | Generates a new secret, stores its peppered hash, and prints the assembled `mxgw_<keyId>_<secret>` token. |
| `list-keys` | none | Lists every stored key with its scopes and revocation state. |
| `create-key` | `--key-id`, `--display-name` | Generates a new secret, stores its peppered hash and optional constraints, and prints the assembled `mxgw_<keyId>_<secret>` token. |
| `list-keys` | none | Lists every stored key with its scopes, constraints, and revocation state. |
| `revoke-key` | `--key-id` | Sets `revoked_utc` if the key is currently active. |
| `rotate-key` | `--key-id` | Replaces the secret hash and prints the new token. |
@@ -203,11 +209,18 @@ Examples:
```bash
mxgateway apikey init-db
mxgateway apikey create-key --key-id ops.alice --display-name "Alice (ops)" --scopes read,write
mxgateway apikey create-key --key-id area1.reader --display-name "Area 1 reader" --scopes invoke:read,metadata:read --read-subtree "Area1/*" --browse-subtree "Area1/*"
mxgateway apikey list-keys --json
mxgateway apikey revoke-key --key-id ops.alice
mxgateway apikey rotate-key --key-id ops.alice
```
Constraint flags are optional. `--read-subtree`, `--write-subtree`,
`--read-tag-glob`, `--write-tag-glob`, and `--browse-subtree` are repeatable.
`--max-write-classification` accepts one integer. `--read-alarm-only` and
`--read-historized-only` are boolean flags. Existing rows with null
constraints remain fully unconstrained after migration.
Key ids are restricted by the parser to ASCII letters, digits, periods, and hyphens so they remain safe to embed in the token format and in URL paths used by administrative tooling.
## Scope Serialization
+45 -2
View File
@@ -1,6 +1,8 @@
# Gateway gRPC Authorization
The authorization subsystem enforces per-RPC scope checks against the authenticated `ApiKeyIdentity` produced by the authentication layer, so service implementations never need to repeat permission logic.
The authorization subsystem has two layers. The gRPC interceptor enforces the
verb scope required by the RPC. Service-layer constraint checks then narrow
what an authenticated API key can browse, read, or write inside the Galaxy.
## Overview
@@ -12,6 +14,8 @@ The participating types live under `src/MxGateway.Server/Security/Authorization/
- `GatewayGrpcScopeResolver` maps a request message (and, for `MxCommandRequest`, the inner `MxCommandKind`) to the scope string that must be present on the caller.
- `GatewayScopes` exposes the canonical scope constants used by the resolver and any downstream consumer.
- `GatewayRequestIdentityAccessor` and `IGatewayRequestIdentityAccessor` expose the verified identity to handlers and any service code that runs inside the call.
- `IConstraintEnforcer` applies optional API-key constraints against the
cached Galaxy hierarchy from service bodies.
- `GrpcAuthorizationServiceCollectionExtensions` wires the components into the DI container and the gRPC pipeline.
The `ApiKeyIdentity` consumed here is produced by the authentication layer; see [Authentication](./Authentication.md) for how it is built and how scopes are persisted.
@@ -21,7 +25,9 @@ The `ApiKeyIdentity` consumed here is produced by the authentication layer; see
Centralizing the policy in `GatewayGrpcAuthorizationInterceptor` produces three concrete benefits:
1. Every RPC defined in `MxAccessGatewayService` is covered by construction. A new RPC inherits the check the moment its request type is added to `GatewayGrpcScopeResolver`, instead of relying on each service method to remember to call an authorization helper.
2. The service class stays a thin translator between proto contracts and domain calls. RPC methods do not branch on identity or scope, which keeps the AGENTS.md guideline that gRPC handlers contain no policy.
2. Verb-scope policy stays centralized. Request-specific constraints still run
in service bodies because they need command payloads, item handles, and
Galaxy metadata that the interceptor should not inspect.
3. Authentication and authorization happen in one place, so the gRPC `Status` mapping is consistent. A failed key check always returns `Unauthenticated`, and a missing scope always returns `PermissionDenied` with the offending scope name.
## Interceptor Flow
@@ -131,6 +137,43 @@ private static string ResolveCommandScope(MxCommandKind kind)
Reads (`Register`, `AddItem`, `Advise`, and any other unspecified kind) fall through to `InvokeRead`, which keeps the matrix small while still separating reads from writes, secured writes, metadata lookups, event drains, and worker shutdown.
## Constraint Enforcement
`ApiKeyIdentity.Constraints` is optional. Empty constraints preserve the
previous behavior: the key is authorized only by its verb scopes. Non-empty
constraints are stored as JSON in `api_keys.constraints` and are applied by
`IConstraintEnforcer` after the interceptor succeeds.
Supported constraints are:
| Constraint | Meaning |
|------------|---------|
| `read_subtrees` | Contained-path globs allowed for read/subscription commands. |
| `write_subtrees` | Contained-path globs allowed for write commands. |
| `read_tag_globs` | Tag-address globs allowed for read/subscription commands. |
| `write_tag_globs` | Tag-address globs allowed for write commands. |
| `max_write_classification` | Maximum Galaxy attribute `security_classification` a key may write. |
| `browse_subtrees` | Contained-path globs used to filter Galaxy browse results and deploy-event counts. |
| `read_alarm_only` | Read/subscription commands must target objects with alarm-bearing attributes. |
| `read_historized_only` | Read/subscription commands must target objects with historized attributes. |
Glob matching is anchored, case-insensitive, and supports `*` and `?`.
Subtree and tag glob lists are alternatives: matching either list allows that
scope dimension. Empty lists mean unconstrained for that dimension.
The service checks read constraints for `AddItem`, `AddItem2`, `AddItemBulk`,
`SubscribeBulk`, and `AdviseItemBulk`. It checks write constraints for
`Write`, `Write2`, `WriteSecured`, and `WriteSecured2`. Successful item
registrations are tracked per session so later item-handle commands resolve
back to the original tag address. If a constrained key presents an unknown item
handle, the gateway fails closed.
Non-bulk constraint failures return gRPC `PermissionDenied`. Bulk read
commands preserve input order and return a failed `SubscribeResult` for each
denied item while still forwarding allowed items to the worker. Every denial
adds an `api_key_audit` entry with the key id, command kind, target, and
blocking constraint; secured values and raw credentials are never logged.
## Scope Catalog
`GatewayScopes` is the single source of truth for scope strings. Every entry is currently mapped by either the resolver or another security component:
+2 -2
View File
@@ -101,6 +101,6 @@ fixtures and validate deterministic wrapper expectation files.
## Related Documentation
- [Client Proto Generation](./client-proto-generation.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
- [Protobuf Contracts](./Contracts.md)
@@ -20,15 +20,15 @@ Target client languages:
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`
- `clients/dotnet/DotnetClientDesign.md`
- `clients/go/GoClientDesign.md`
- `clients/rust/RustClientDesign.md`
- `clients/python/PythonClientDesign.md`
- `clients/java/JavaClientDesign.md`
Shared generation inputs:
- `docs/client-proto-generation.md`
- `docs/ClientProtoGeneration.md`
- `docs/ClientBehaviorFixtures.md`
- `docs/ClientPackaging.md`
- `clients/proto/proto-inputs.json`
@@ -433,3 +433,15 @@ Each client README should include:
- integration test instructions,
- warning that canceling a client call does not abort an in-flight MXAccess COM
call.
## Related Documentation
- [.NET 10 C# Client Detailed Design](../clients/dotnet/DotnetClientDesign.md)
- [Go Client Detailed Design](../clients/go/GoClientDesign.md)
- [Rust Client Detailed Design](../clients/rust/RustClientDesign.md)
- [Python Client Detailed Design](../clients/python/PythonClientDesign.md)
- [Java Client Detailed Design](../clients/java/JavaClientDesign.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
- [Client Packaging](./ClientPackaging.md)
- [Cross-Language Smoke Matrix](./CrossLanguageSmokeMatrix.md)
+4 -4
View File
@@ -2,7 +2,7 @@
This document defines the clean-checkout commands for building, packaging, and
running the official MXAccess Gateway clients. Use the tool paths and versions
in [Toolchain Links](./toolchain-links.md) when a command is missing from
in [Toolchain Links](./ToolchainLinks.md) when a command is missing from
`PATH`.
## Shared Inputs
@@ -254,7 +254,7 @@ does not abort an MXAccess COM call that is already executing on the worker STA.
## Related Documentation
- [Client Proto Generation](./client-proto-generation.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
- [Toolchain Links](./toolchain-links.md)
- [Toolchain Links](./ToolchainLinks.md)
@@ -199,8 +199,8 @@ scripts/validate-client-behavior-fixtures.ps1
## Related Documentation
- [Protobuf Contracts](./Contracts.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
- [Client Packaging](./ClientPackaging.md)
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
- [Client Libraries Implementation Plan](./implementation-plan-clients.md)
- [Client Libraries Implementation Plan](./ImplementationPlanClients.md)
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
+4 -4
View File
@@ -41,7 +41,7 @@ hand-edit generated files.
Client generation inputs are published through
`clients/proto/proto-inputs.json` and the descriptor set under
`clients/proto/descriptors/`. See
[Client Proto Generation](./client-proto-generation.md) for language-specific
[Client Proto Generation](./ClientProtoGeneration.md) for language-specific
generation inputs, output directories, and golden protobuf JSON fixtures.
## Generation
@@ -73,7 +73,7 @@ powershell -ExecutionPolicy Bypass -File scripts/publish-client-proto-inputs.ps1
## Related Documentation
- [Client Proto Generation](./client-proto-generation.md)
- [Gateway Process Detailed Design](./gateway-process-design.md)
- [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [MXAccess Worker Instance Detailed Design](./MxAccessWorkerInstanceDesign.md)
- [Protobuf Style Guide](./style-guides/ProtobufStyleGuide.md)
+2 -2
View File
@@ -94,5 +94,5 @@ gateway, the installed MXAccess worker path, and provider state.
## Related Documentation
- [Gateway Testing](./GatewayTesting.md)
- [Client Libraries Detailed Design](./client-libraries-design.md)
- [Client Proto Generation](./client-proto-generation.md)
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
+1 -1
View File
@@ -291,4 +291,4 @@ Use this checklist when applying the design to another project:
## Related Documentation
- [Gateway Dashboard Detailed Design](./gateway-dashboard-design.md)
- [Gateway Dashboard Detailed Design](./GatewayDashboardDesign.md)
@@ -308,3 +308,11 @@ These are explicit post-v1 revisit items, not open blockers:
- restricted worker service account,
- production coalescing by item handle,
- command batching for high-volume tag setup.
## Related Documentation
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [MXAccess Worker Instance Detailed Design](./MxAccessWorkerInstanceDesign.md)
- [Authentication](./Authentication.md)
- [Authorization](./Authorization.md)
- [Galaxy Repository](./GalaxyRepository.md)
+63 -16
View File
@@ -32,13 +32,23 @@ The service is defined in
|-----|---------|
| `TestConnection` | Connectivity probe. Returns `{ ok: bool }` after a `SELECT 1`. Does not throw on SQL failure — returns `ok = false`. Always hits SQL directly so it remains a true health check. |
| `GetLastDeployTime` | Returns the cached `galaxy.time_of_last_deploy`. Served from the shared hierarchy cache; refreshed in the background. |
| `DiscoverHierarchy` | Returns the full deployed hierarchy plus every object's dynamic attributes. **Served from cache** — see [Hierarchy Cache](#hierarchy-cache). |
| `DiscoverHierarchy` | Returns one page of the deployed hierarchy plus each returned object's dynamic attributes. **Served from cache** — see [Hierarchy Cache](#hierarchy-cache). |
| `WatchDeployEvents` | **Server-streaming.** The server emits the current state immediately on subscribe (so clients can bootstrap without waiting), then emits one event per detected deploy change. See [Deploy Notifications](#deploy-notifications). |
`DiscoverHierarchy` is intentionally a single unary RPC rather than a stream:
the row set is small (thousands of objects, low tens-of-thousands of
attributes for typical Galaxies) and clients almost always want the whole tree
at once.
`DiscoverHierarchy` is a paged unary RPC. The raw request accepts `page_size`
and `page_token`; the server defaults omitted page size to 1000 objects and
caps every page at 5000 objects. Page tokens bind to the cache sequence and the
active filter set, so changing filters between pages returns `InvalidArgument`
instead of mixing snapshots. Official high-level clients preserve the older
"return the full hierarchy" behavior by looping pages internally.
The request can also slice the cached hierarchy without running new SQL. A
caller may choose one root (`root_gobject_id`, `root_tag_name`, or
`root_contained_path`) and may combine that with `max_depth`, category ids,
template-chain substring filters, an anchored case-insensitive tag-name glob,
alarm-only, historized-only, and `include_attributes = false` for a skeleton
tree. All filters are applied with AND semantics, and `total_object_count`
reports the post-filter count.
## Hierarchy Cache
@@ -56,12 +66,14 @@ Refresh strategy is **deploy-time gated**:
3. If the deploy timestamp is unchanged, the heavy hierarchy + attributes
queries are **skipped**. The cache simply marks `LastSuccessAt`.
4. If the deploy timestamp changed (or no data has loaded yet), the cache
pulls hierarchy + attributes, materializes a `DiscoverHierarchyReply`
once, replaces the entry atomically, and publishes a deploy event.
pulls hierarchy + attributes, materializes a Galaxy object list plus a
dashboard summary once, replaces the entry atomically, and publishes a
deploy event.
Materializing the reply at refresh time means subsequent `DiscoverHierarchy`
calls return a pre-built proto message — no per-request projection, no
per-request allocations beyond the gRPC serializer's frame.
Materializing objects and dashboard summaries at refresh time means subsequent
`DiscoverHierarchy` calls page over an immutable object list. The dashboard
uses the precomputed summary and does not rescan raw SQL rowsets on each
snapshot.
When SQL is unreachable, the cache retains the previous data and flips
`Status` to `Stale` (or `Unavailable` if no data was ever loaded). A
@@ -110,7 +122,7 @@ Typical client pattern:
3. If sequence skipped a number, treat it as a dropped event and refresh.
```
### Reply Shape
### Reply shape
```proto
message GalaxyObject {
@@ -139,9 +151,32 @@ message GalaxyAttribute {
bool is_historized = 10;
bool is_alarm = 11;
}
message DiscoverHierarchyRequest {
int32 page_size = 1; // omitted/0 uses the server default of 1000
string page_token = 2; // opaque token returned by the previous page
oneof root {
int32 root_gobject_id = 3;
string root_tag_name = 4;
string root_contained_path = 5;
}
google.protobuf.Int32Value max_depth = 6;
repeated int32 category_ids = 7;
repeated string template_chain_contains = 8;
string tag_name_glob = 9;
optional bool include_attributes = 10;
bool alarm_bearing_only = 11;
bool historized_only = 12;
}
message DiscoverHierarchyReply {
repeated GalaxyObject objects = 1;
string next_page_token = 2;
int32 total_object_count = 3;
}
```
### Contained Name vs Tag Name
### Contained name vs tag name
Galaxy objects carry two names. `tag_name` is globally unique and is what
MXAccess expects in `AddItem`. `contained_name` is the human-readable name
@@ -150,7 +185,7 @@ both: clients display `browse_name` to users and pass `tag_name` (or
`full_tag_reference`) into MXAccess subscriptions. When `contained_name` is
empty (top-level objects), `browse_name` falls back to `tag_name`.
### Data Types
### Data types
`mx_data_type` is returned as the raw Galaxy integer rather than mapped to a
language-neutral enum. The gateway makes no assumption about the client's
@@ -176,7 +211,8 @@ GalaxyHierarchyRefreshService (BackgroundService)
-> GalaxyRepository.GetLastDeployTimeAsync (cheap, every tick)
-> GalaxyRepository.GetHierarchyAsync (only on deploy change)
-> GalaxyRepository.GetAttributesAsync (only on deploy change)
-> GalaxyProtoMapper.MapObject (materialize DiscoverHierarchyReply once)
-> GalaxyProtoMapper.MapObject (materialize GalaxyObject list once)
-> DashboardGalaxySummary (precompute dashboard counts once)
-> IGalaxyDeployNotifier.Publish (only on deploy change)
```
@@ -189,8 +225,9 @@ Component breakdown:
recursive CTEs and pick the most-derived attribute override per object.
- `GalaxyHierarchyCache`
(`src/MxGateway.Server/Galaxy/GalaxyHierarchyCache.cs`) holds the most
recent immutable `GalaxyHierarchyCacheEntry` (rows + materialized proto
reply + counts + status). All gRPC clients share the same entry.
recent immutable `GalaxyHierarchyCacheEntry` (materialized objects +
precomputed dashboard summary + counts + status). All gRPC clients share the
same entry.
- `GalaxyHierarchyRefreshService`
(`src/MxGateway.Server/Galaxy/GalaxyHierarchyRefreshService.cs`) is a
hosted `BackgroundService` that drives `RefreshAsync` on the configured
@@ -220,6 +257,11 @@ Security`), but production deployments that use SQL authentication should set
the override via environment variable rather than committing credentials to
`appsettings.json`.
The dashboard parses this connection string and displays only non-secret
fields: server, database, integrated security, encrypt, and trust-server-
certificate. It never displays user id, password, access token, or arbitrary
unparsed connection string text.
## Authorization
All four Galaxy RPCs (including `WatchDeployEvents`) require the
@@ -228,6 +270,11 @@ privilege to `MxCommandKind.GetSessionState` or `MxCommandKind.GetWorkerInfo`.
The mapping lives in `GatewayGrpcScopeResolver`; see
[Authorization](./Authorization.md) for the full scope catalog.
API keys can also carry `browse_subtrees` constraints. `DiscoverHierarchy`
intersects those contained-path globs with the caller's request filters.
`WatchDeployEvents` still emits deploy notifications, but its object and
attribute counts are scoped to the caller's browsable subtrees.
A request without an API key returns `Unauthenticated`. A request with a key
that lacks `metadata:read` returns `PermissionDenied` with the missing scope
embedded in the status detail.
+9 -3
View File
@@ -35,6 +35,8 @@ paths, timeouts, queue sizes, enum values, or protocol values are invalid.
"DefaultCommandTimeoutSeconds": 30,
"MaxSessions": 64,
"MaxPendingCommandsPerSession": 128,
"DefaultLeaseSeconds": 1800,
"LeaseSweepIntervalSeconds": 30,
"AllowMultipleEventSubscribers": false
},
"Events": {
@@ -52,7 +54,8 @@ paths, timeouts, queue sizes, enum values, or protocol values are invalid.
"ShowTagValues": false
},
"Protocol": {
"WorkerProtocolVersion": 1
"WorkerProtocolVersion": 1,
"MaxGrpcMessageBytes": 16777216
},
"Galaxy": {
"ConnectionString": "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;",
@@ -107,6 +110,8 @@ to avoid accidental large allocations from malformed or oversized frames.
| `MxGateway:Sessions:DefaultCommandTimeoutSeconds` | `30` | Default timeout used while the gateway waits for a worker command reply when an open-session request does not provide a positive command timeout. |
| `MxGateway:Sessions:MaxSessions` | `64` | Maximum number of concurrently open gateway sessions. Session opens reserve a slot atomically before worker creation. |
| `MxGateway:Sessions:MaxPendingCommandsPerSession` | `128` | Maximum number of pending worker commands for one session. Excess commands fail fast instead of queueing indefinitely. |
| `MxGateway:Sessions:DefaultLeaseSeconds` | `1800` | Initial session lease and refresh duration. Unary client activity extends the lease by this duration. |
| `MxGateway:Sessions:LeaseSweepIntervalSeconds` | `30` | Hosted monitor interval for closing expired leases. Active event-stream subscribers keep a session from expiring while the stream remains attached. |
| `MxGateway:Sessions:AllowMultipleEventSubscribers` | `false` | Controls whether multiple `StreamEvents` subscribers may attach to one session. `true` is rejected until event fan-out is implemented. |
All numeric session options must be greater than zero. The current event stream
@@ -146,6 +151,7 @@ and `RecentSessionLimit` must be greater than or equal to zero.
| Option | Default | Description |
|--------|---------|-------------|
| `MxGateway:Protocol:WorkerProtocolVersion` | `1` | Worker IPC protocol version expected by the gateway and worker. This must match `GatewayContractInfo.WorkerProtocolVersion`. |
| `MxGateway:Protocol:MaxGrpcMessageBytes` | `16777216` | Public gRPC max send and receive message size in bytes. The same default is used by official clients. The validator allows values from `1024` through `268435456`. |
The protocol option is exposed for diagnostics and explicit deployment
configuration, not for compatibility negotiation. A mismatch fails validation
@@ -164,8 +170,8 @@ behavior.
## Related Documentation
- [Gateway Process Detailed Design](./gateway-process-design.md)
- [Gateway Dashboard Detailed Design](./gateway-dashboard-design.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [Gateway Dashboard Detailed Design](./GatewayDashboardDesign.md)
- [Worker Process Launcher](./WorkerProcessLauncher.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
- [Galaxy Repository Browse](./GalaxyRepository.md)
@@ -165,7 +165,7 @@ counts and rates instead.
## Pages
### Dashboard Home
### Dashboard home
Show top-level status:
@@ -184,7 +184,7 @@ Show top-level status:
Use Bootstrap cards for individual metric summaries. Keep the layout compact
and operational.
### Sessions Page
### Sessions page
Show active and recent sessions in a table:
@@ -203,7 +203,7 @@ Show active and recent sessions in a table:
Rows should link to session details.
### Session Details Page
### Session details page
Show:
@@ -219,7 +219,7 @@ Show:
For v1, details should be read-only unless an explicit admin action design is
added.
### Workers Page
### Workers page
Show:
@@ -235,7 +235,7 @@ Show:
- event queue depth,
- restart/kill reason if terminal.
### Events Page
### Events page
Show aggregate event diagnostics:
@@ -249,7 +249,7 @@ Show aggregate event diagnostics:
Do not display full tag values by default. If value display is later added, make
it opt-in and redacted.
### Settings Page
### Settings page
Show read-only effective configuration:
@@ -390,3 +390,13 @@ The first dashboard slice implements:
11. periodic realtime refresh through Blazor Server.
12. route-mapping tests, disabled-dashboard tests, auth tests, and snapshot
projection/redaction tests.
## Related Documentation
- [Dashboard Interface Design](./DashboardInterfaceDesign.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [Authentication](./Authentication.md)
- [Authorization](./Authorization.md)
- [Sessions](./Sessions.md)
- [Metrics](./Metrics.md)
- [Diagnostics](./Diagnostics.md)
@@ -314,7 +314,7 @@ Faulted
-> Closed
```
### State Rules
### State rules
- `Creating`: session id and in-memory state exist, but no worker has launched.
- `StartingWorker`: worker process launch is in progress.
@@ -432,7 +432,7 @@ Recommended size limits:
- reject zero-length payloads,
- reject payloads larger than configured maximum before allocation.
### Envelope Rules
### Envelope rules
Every message uses `WorkerEnvelope`:
@@ -489,7 +489,7 @@ and nonce, waits for `WorkerReady`, and only then exposes `Ready` state. The
read loop starts after readiness so the handshake has a single owner for its
ordered frames.
### Read Loop
### Read loop
The read loop:
@@ -505,7 +505,7 @@ The read loop:
If the pipe closes while the session is not closing, fault the session.
### Write Loop
### Write loop
The write loop serializes all writes to the pipe. No other code should write to
the pipe directly.
@@ -972,3 +972,17 @@ The first gateway slice should implement:
17. Basic structured logs.
This proves the process model before the full command surface is implemented.
## Related Documentation
- [MXAccess Worker Instance Detailed Design](./MxAccessWorkerInstanceDesign.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
- [Worker Process Launcher](./WorkerProcessLauncher.md)
- [Gateway Configuration](./GatewayConfiguration.md)
- [Sessions](./Sessions.md)
- [gRPC](./Grpc.md)
- [Authentication](./Authentication.md)
- [Authorization](./Authorization.md)
- [Metrics](./Metrics.md)
- [Diagnostics](./Diagnostics.md)
- [Gateway Testing](./GatewayTesting.md)
+2 -2
View File
@@ -161,6 +161,6 @@ dotnet test src/MxGateway.Tests/MxGateway.Tests.csproj
- [Cross-Language Smoke Matrix](./CrossLanguageSmokeMatrix.md)
- [Parity Fixture Matrix](./ParityFixtureMatrix.md)
- [Gateway Process Design](./gateway-process-design.md)
- [Gateway Process Design](./GatewayProcessDesign.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
- [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md)
- [MXAccess Worker Instance Detailed Design](./MxAccessWorkerInstanceDesign.md)
+7 -2
View File
@@ -31,6 +31,11 @@ A second gRPC service, `GalaxyRepositoryGrpcService`, is mapped alongside it. It
`MxAccessGatewayService` derives from the generated `MxAccessGateway.MxAccessGatewayBase` and implements every RPC declared in `mxaccess_gateway.proto`. The proto contract itself is documented in [Contracts](./Contracts.md); this section covers only what the server-side handler does on top of that contract.
Public gRPC send and receive message sizes are configured from
`MxGateway:Protocol:MaxGrpcMessageBytes` (default 16 MiB). Official clients use
the same default so paged Galaxy browse replies and larger MXAccess payloads
fail consistently instead of depending on language-specific gRPC defaults.
### `OpenSession`
`OpenSession` validates the request, asks `ISessionManager` to open a session under the caller's identity, and returns a reply that advertises both protocol versions and the capabilities the gateway supports. Capability strings are static because the gateway has a fixed feature set per build; clients use them as a forward-compatibility hint rather than runtime negotiation.
@@ -211,7 +216,7 @@ if (!writer.TryWrite(publicEvent))
Under `FailFast` the session is faulted so subsequent commands return `FailedPrecondition`; the client must reopen. Under the default policy only the stream is dropped and the session continues to accept commands, leaving recovery to the client (typically a fresh `StreamEvents` call with an updated `AfterWorkerSequence`). Either way, the consumer side observes `StatusCode.ResourceExhausted` via the `EventQueueOverflow` mapping above.
### Cancellation and Cleanup
### Cancellation and cleanup
The handler creates a linked cancellation token (`streamCts`) so that completing the consumer (client disconnect, error, or graceful end-of-stream) also cancels the producer. The `finally` block cancels the source, disposes the subscriber slot, awaits the producer (swallowing the expected cancellation), and emits `StreamDisconnected("Detached")` so dashboards see the disconnection regardless of cause.
@@ -233,4 +238,4 @@ Because the interceptor runs before any handler, `MxAccessGatewayService` can sa
- [Contracts](./Contracts.md)
- [Sessions](./Sessions.md)
- [Authorization](./Authorization.md)
- [Gateway Process Design](./gateway-process-design.md)
- [Gateway Process Design](./GatewayProcessDesign.md)
@@ -5,13 +5,13 @@ 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`
- `docs/ClientLibrariesDesign.md`
- `clients/dotnet/DotnetClientDesign.md`
- `clients/go/GoClientDesign.md`
- `clients/rust/RustClientDesign.md`
- `clients/python/PythonClientDesign.md`
- `clients/java/JavaClientDesign.md`
- `docs/ToolchainLinks.md`
## Shared Milestone: client-contracts-and-fixtures
@@ -382,6 +382,15 @@ Deliverables:
Acceptance criteria:
- new developer can build each client from a clean checkout using
`docs/toolchain-links.md`,
`docs/ToolchainLinks.md`,
- generated code command is documented for every language.
## Related Documentation
- [Implementation Plan Index](./ImplementationPlanIndex.md)
- [Client Libraries Detailed Design](./ClientLibrariesDesign.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
- [Client Behavior Fixtures](./ClientBehaviorFixtures.md)
- [Client Packaging](./ClientPackaging.md)
- [Cross-Language Smoke Matrix](./CrossLanguageSmokeMatrix.md)
@@ -6,10 +6,10 @@ 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`
- `docs/GatewayProcessDesign.md`
- `docs/GatewayDashboardDesign.md`
- `docs/DesignDecisions.md`
- `docs/ToolchainLinks.md`
## Milestone: gateway-foundation
@@ -509,3 +509,17 @@ Acceptance criteria:
- worker exits,
- artifacts stay in temp directories.
## Related Documentation
- [Implementation Plan Index](./ImplementationPlanIndex.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [Gateway Configuration](./GatewayConfiguration.md)
- [Sessions](./Sessions.md)
- [gRPC](./Grpc.md)
- [Authentication](./Authentication.md)
- [Authorization](./Authorization.md)
- [Gateway Dashboard Detailed Design](./GatewayDashboardDesign.md)
- [Gateway Testing](./GatewayTesting.md)
- [Metrics](./Metrics.md)
- [Diagnostics](./Diagnostics.md)
@@ -17,9 +17,9 @@ Implementation order:
Detailed plans:
- `docs/implementation-plan-gateway.md`
- `docs/implementation-plan-mxaccess-worker.md`
- `docs/implementation-plan-clients.md`
- `docs/ImplementationPlanGateway.md`
- `docs/ImplementationPlanMxAccessWorker.md`
- `docs/ImplementationPlanClients.md`
## Gitea Milestones
@@ -91,10 +91,17 @@ Every implementation issue should meet this baseline:
## Toolchain
Use `docs/toolchain-links.md` for installed compiler/runtime paths. If a new
Use `docs/ToolchainLinks.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')
```
## Related Documentation
- [Gateway Implementation Plan](./ImplementationPlanGateway.md)
- [MXAccess Worker Implementation Plan](./ImplementationPlanMxAccessWorker.md)
- [Client Libraries Implementation Plan](./ImplementationPlanClients.md)
- [Toolchain Links](./ToolchainLinks.md)
@@ -7,9 +7,9 @@ and shutdown.
Primary designs:
- `docs/mxaccess-worker-instance-design.md`
- `docs/design-decisions.md`
- `docs/toolchain-links.md`
- `docs/MxAccessWorkerInstanceDesign.md`
- `docs/DesignDecisions.md`
- `docs/ToolchainLinks.md`
- `C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Public-API.md`
## Milestone: mxaccess-worker-foundation
@@ -29,7 +29,7 @@ Deliverables:
- reference generated worker contracts,
- reference `ArchestrA.MXAccess.dll`,
- create `src/MxGateway.Worker.Tests`,
- document MSBuild command from `docs/toolchain-links.md`.
- document MSBuild command from `docs/ToolchainLinks.md`.
Acceptance criteria:
@@ -453,3 +453,14 @@ Acceptance criteria:
- each public method has planned parity fixture or documented gap,
- gateway results preserve HRESULT/status/value/event shape.
## Related Documentation
- [Implementation Plan Index](./ImplementationPlanIndex.md)
- [MXAccess Worker Instance Detailed Design](./MxAccessWorkerInstanceDesign.md)
- [Worker Bootstrap](./WorkerBootstrap.md)
- [Worker STA](./WorkerSta.md)
- [Worker Conversion](./WorkerConversion.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
- [Worker Process Launcher](./WorkerProcessLauncher.md)
- [Parity Fixture Matrix](./ParityFixtureMatrix.md)
+3 -3
View File
@@ -64,7 +64,7 @@ _eventStreamSendLatencyHistogram = _meter.CreateHistogram<double>("mxgateway.eve
| `mxgateway.commands.duration` | `method`, optional `category` | Command round-trip time. The `category` tag is added on failure so success and failure latencies stay distinguishable. |
| `mxgateway.events.stream_send.duration` | `family` | Time spent writing each public event to the gRPC response stream in `MxAccessGatewayService.StreamEvents`. |
### Observable Gauges
### Observable gauges
Observable gauges are pull-based; the `Meter` invokes the supplied callback whenever a listener samples it. Each callback re-acquires `_syncRoot` so the gauge value matches the snapshot taken at the same instant.
@@ -201,10 +201,10 @@ metrics.RecordEventStreamSend(publicEvent.Family.ToString(), stopwatch.Elapsed);
## Dashboard Consumption
`Dashboard/DashboardSnapshotService.cs` calls `_metrics.GetSnapshot()` once per `GetSnapshot` invocation and projects it into the dashboard transport types together with the session registry view. The dashboard receives a single, internally consistent snapshot per tick rather than reading individual counters at separate times. See [Gateway Dashboard Design](./gateway-dashboard-design.md) and [Dashboard Interface Design](./DashboardInterfaceDesign.md) for the projection rules and wire format.
`Dashboard/DashboardSnapshotService.cs` calls `_metrics.GetSnapshot()` once per `GetSnapshot` invocation and projects it into the dashboard transport types together with the session registry view. The dashboard receives a single, internally consistent snapshot per tick rather than reading individual counters at separate times. See [Gateway Dashboard Design](./GatewayDashboardDesign.md) and [Dashboard Interface Design](./DashboardInterfaceDesign.md) for the projection rules and wire format.
## Related Documentation
- [Gateway Dashboard Design](./gateway-dashboard-design.md)
- [Gateway Dashboard Design](./GatewayDashboardDesign.md)
- [Dashboard Interface Design](./DashboardInterfaceDesign.md)
- [Sessions](./Sessions.md)
@@ -36,7 +36,7 @@ installation:
dotnet msbuild src\MxGateway.Worker\MxGateway.Worker.csproj /restore /p:Configuration=Debug /p:Platform=x86
```
`docs/toolchain-links.md` records the Visual Studio MSBuild executable for
`docs/ToolchainLinks.md` records the Visual Studio MSBuild executable for
classic .NET Framework and COM interop builds:
```powershell
@@ -836,3 +836,13 @@ The first worker slice should implement:
This slice proves the worker can preserve the core MXAccess requirements:
single-process isolation, STA ownership, message pumping, command execution,
and event delivery.
## Related Documentation
- [Worker Bootstrap](./WorkerBootstrap.md)
- [Worker STA](./WorkerSta.md)
- [Worker Conversion](./WorkerConversion.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
- [Worker Process Launcher](./WorkerProcessLauncher.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [Design Decisions](./DesignDecisions.md)
+1 -1
View File
@@ -98,5 +98,5 @@ normal unit tests only validate the repository fixture shape.
## Related Documentation
- [Gateway Testing](./GatewayTesting.md)
- [MXAccess Worker Instance Detailed Design](./mxaccess-worker-instance-design.md)
- [MXAccess Worker Instance Detailed Design](./MxAccessWorkerInstanceDesign.md)
- [Protobuf Contracts](./Contracts.md)
+3 -3
View File
@@ -178,9 +178,9 @@ The order — fault, deregister, dispose, release slot, record metric, log, reth
While `Ready`, callers reach the worker through `SessionManager.InvokeAsync` or `ReadEventsAsync`. Both delegate to `GatewaySession`, which checks the state under lock and updates `LastClientActivityAt` on every invocation. `GatewaySession` also exposes typed bulk helpers (`AddItemBulkAsync`, `SubscribeBulkAsync`, etc.) that wrap `WorkerCommand` round-trips and translate non-`Ok` `ProtocolStatus` replies into `SessionManagerException` with `SessionNotReady`.
Event streaming uses `AttachEventSubscriber` which returns a disposable lease. When `allowMultipleSubscribers` is false the second attach throws `EventSubscriberAlreadyActive`; this prevents two gRPC streams from racing on the same worker event channel.
Event streaming uses `AttachEventSubscriber` which returns a disposable lease. When `allowMultipleSubscribers` is false the second attach throws `EventSubscriberAlreadyActive`; this prevents two gRPC streams from racing on the same worker event channel. Active event subscribers keep the session lease from expiring until the stream is disposed.
`ExtendLease` and `IsLeaseExpired` cooperate with `SessionManager.CloseExpiredLeasesAsync`, which iterates a registry snapshot and closes any session whose lease has expired with `LeaseExpiredReason`.
Sessions open with `MxGateway:Sessions:DefaultLeaseSeconds` (default 1800) added to the open timestamp. Unary client activity refreshes the lease by the same duration. `ExtendLease` and `IsLeaseExpired` cooperate with `SessionManager.CloseExpiredLeasesAsync`, which iterates a registry snapshot and closes any session whose lease has expired with `LeaseExpiredReason`. `SessionLeaseMonitorHostedService` runs that sweep every `MxGateway:Sessions:LeaseSweepIntervalSeconds` seconds (default 30).
### Close
@@ -266,6 +266,6 @@ The registry must be a singleton because its `ConcurrentDictionary` is the sourc
## Related Documentation
- [Gateway Process Design](./gateway-process-design.md)
- [Gateway Process Design](./GatewayProcessDesign.md)
- [Gateway Configuration](./GatewayConfiguration.md)
- [Worker Process Launcher](./WorkerProcessLauncher.md)
@@ -170,3 +170,9 @@ These checks passed after installation:
- `javac` compile of a temporary Java class.
- Python imports for `grpc`, `grpc_tools`, and `pytest`.
## Related Documentation
- [Implementation Plan Index](./ImplementationPlanIndex.md)
- [Client Proto Generation](./ClientProtoGeneration.md)
- [Client Packaging](./ClientPackaging.md)
+1 -1
View File
@@ -257,6 +257,6 @@ The exception is distinct from `COMException` so the worker's command pipeline c
## Related Documentation
- [MXAccess Worker Instance Design](./mxaccess-worker-instance-design.md)
- [MXAccess Worker Instance Design](./MxAccessWorkerInstanceDesign.md)
- [Contracts](./Contracts.md)
- [Worker STA Threading](./WorkerSta.md)
+1 -1
View File
@@ -50,5 +50,5 @@ dotnet build src/MxGateway.Server/MxGateway.Server.csproj
## Related Documentation
- [Gateway Process Detailed Design](./gateway-process-design.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [Protobuf Contracts](./Contracts.md)
+1 -1
View File
@@ -71,5 +71,5 @@ dotnet build src/MxGateway.Server/MxGateway.Server.csproj
## Related Documentation
- [Gateway Process Detailed Design](./gateway-process-design.md)
- [Gateway Process Detailed Design](./GatewayProcessDesign.md)
- [Worker Frame Protocol](./WorkerFrameProtocol.md)
+1 -1
View File
@@ -152,6 +152,6 @@ finally
## Related Documentation
- [MXAccess worker instance design](./mxaccess-worker-instance-design.md)
- [MXAccess worker instance design](./MxAccessWorkerInstanceDesign.md)
- [Worker conversion](./WorkerConversion.md)
- [Worker bootstrap](./WorkerBootstrap.md)
+7 -7
View File
@@ -42,25 +42,25 @@ transport.
Detailed follow-up docs:
- `docs/gateway-process-design.md` covers the .NET 10 gateway process,
- `docs/GatewayProcessDesign.md` covers the .NET 10 gateway process,
session manager, worker supervision, gRPC API, event streaming, fault model,
security, observability, and test strategy.
- `docs/WorkerFrameProtocol.md` covers the gateway-side named-pipe frame
reader/writer and `WorkerEnvelope` validation rules.
- `docs/WorkerProcessLauncher.md` covers worker executable validation, process
launch arguments, nonce handling, and startup cleanup behavior.
- `docs/mxaccess-worker-instance-design.md` covers each .NET Framework 4.8 x86
- `docs/MxAccessWorkerInstanceDesign.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
- `docs/DesignDecisions.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
- `docs/GatewayDashboardDesign.md` covers the Blazor Server and Bootstrap
dashboard for live gateway/session/worker status.
- `docs/client-libraries-design.md` covers shared design requirements for
- `docs/ClientLibrariesDesign.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
- `docs/ImplementationPlanIndex.md` links the detailed implementation plans
and recommended Gitea milestones/issues.
- `docs/GalaxyRepository.md` covers the read-only Galaxy Repository browse
RPCs that let clients enumerate the deployed object hierarchy and dynamic
@@ -950,7 +950,7 @@ backend = mxaccess-worker
## Open Questions
Current v1 decisions are recorded in `docs/design-decisions.md`.
Current v1 decisions are recorded in `docs/DesignDecisions.md`.
Resolved for v1:
+1 -1
View File
@@ -36,7 +36,7 @@ if ($Attributes.Count -eq 0) {
}
if (-not (Test-Path -LiteralPath $SqlcmdPath)) {
throw "sqlcmd was not found at '$SqlcmdPath'. Pass -SqlcmdPath or update docs/toolchain-links.md."
throw "sqlcmd was not found at '$SqlcmdPath'. Pass -SqlcmdPath or update docs/ToolchainLinks.md."
}
$attributeList = ($Attributes | ForEach-Object {
+1 -1
View File
@@ -22,7 +22,7 @@ function Resolve-Protoc {
return $documentedPath
}
throw "Could not find protoc.exe. See docs/toolchain-links.md for the documented protobuf toolchain path."
throw "Could not find protoc.exe. See docs/ToolchainLinks.md for the documented protobuf toolchain path."
}
function Ensure-Directory {