Commit Graph

5 Commits

Author SHA1 Message Date
Joseph Doherty 41f2d4c0f2 [F14] mxaccess-galaxy: tiberius-backed SQL Resolver + UserResolver
rust / build / test / clippy / fmt (push) Has been cancelled
New module crates/mxaccess-galaxy/src/sql_resolver.rs (~480 LoC) gated
behind the existing galaxy-resolver Cargo feature. Adds SqlTagResolver
+ SqlUserResolver, both constructed via from_ado_string(&str)
accepting the same connection-string shape the .NET reference uses by
default (Server=localhost;Database=ZB;Integrated Security=True;
Encrypt=False;TrustServerCertificate=True). Integrated Security=True
resolves to Windows auth via tiberius's winauth feature.

Each top-level call (resolve / browse / resolve_by_guid /
resolve_by_name) opens a fresh Client<Compat<TcpStream>> and drops it
on return — matches the .NET `await using` lifecycle at
GalaxyRepositoryTagResolver.cs:93-95. tiberius's Client::query only
accepts positional @P1..@PN placeholders (delegates to sp_executesql);
the canonical RESOLVE_SQL / BROWSE_SQL / USER_BY_GUID_SQL /
USER_BY_NAME_SQL constants are rewritten once-per-process via
OnceLock<String> (@objectTagName → @P1, etc.). The unrewritten
constants stay byte-identical with the .NET reference for ad-hoc
diagnostic copy/paste.

read_metadata mirrors ReadMetadata (cs:149-165) byte-by-byte: signed
smallint → i16 widened to u16 for platform/engine/object IDs (matches
the .NET checked((ushort)reader.GetInt16(N)) shape), int → i32
checked-cast to i16 for property_id, nullable nvarchar for
primitive_name. read_user_profile mirrors ReadProfile (cs:76-85)
including the roles_text blob → parse_role_blob round-trip.

Deps added (gated): tiberius 0.12 (default-features = false; tds73 +
rustls + winauth — no chrono / rust_decimal pulled), tokio-util's
compat feature for the futures-rs ↔ tokio AsyncRead bridge,
futures-util for TryStreamExt::try_next. Default-feature build still
pulls only mxaccess-codec + async-trait + thiserror + uuid (slim
foot-print preserved per the design doc's intent).

New `live` feature on this crate (`live = ["galaxy-resolver"]`) for
parity with the workspace pattern.

11 offline unit tests pin: SQL named→positional rewriting (no @named
left, @P1/@P2/@P3 present), line-count preserved, ado-string
acceptance (default Galaxy shape parses, garbage rejected), input
validation (max_rows=0 rejected, empty LIKE rejected, empty user_name
rejected, all checked before connect attempt).

Two #[cfg(feature = "live")] #[ignore]'d tests round-trip against a
real Galaxy DB (gated on MX_LIVE + MX_GALAXY_DB env vars per
tools/Setup-LiveProbeEnv.ps1). Live verification on this host:
live_resolve_test_child_object_test_int and
live_browse_test_child_object both pass against the local AVEVA
install — TestChildObject.TestInt resolves with mx_data_type=2
(Int32), is_array=false.

Closes F14 in design/followups.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:54:43 -04:00
Joseph Doherty 68aa2e30ab [M3] codec+galaxy: MxValueKind::for_data_type + GalaxyTagMetadata::resolve_write_kind
Last codec-side prerequisite before F13 (NmxClient high-level write
wrappers) can land. Two small additions, both wire-byte-direct ports
of the .NET reference's MxDataType → MxValueKind lookup logic.

mxaccess-codec
- MxValueKind::for_data_type(MxDataType, is_array) -> Option<MxValueKind>:
  fuses NmxWriteMessage.cs:58-86 (TryGetValueKind's 12 base mappings
  for data types 1..=6 scalar+array) with the two scalar fallbacks the
  .NET GalaxyTagMetadata.ProjectWriteValue layers on top
  (GalaxyRepositoryTagResolver.cs:65-69): ElapsedTime → Int32,
  InternationalizedString → String. Returns None for any other
  combination — including arrays of those two types and unsupported
  scalars (ReferenceType, StatusType, Enum, etc.).
- 6 new tests covering the base table, both fallbacks, the array-of-
  unsupported rejection, and the no-mapping branch for ReferenceType /
  StatusType / Enum / DataQualityType / BigString / Unknown / NoData /
  End sentinels.

mxaccess-galaxy
- GalaxyTagMetadata::resolve_write_kind() -> Result<MxValueKind,
  UnsupportedDataType>: pure delegation to MxValueKind::for_data_type
  + a typed error carrying (mx_data_type, is_array) for diagnostics.
- GalaxyTagMetadata::is_writable() — Ok-side accessor for browse UIs.
- UnsupportedDataType public error type (re-exported from lib.rs).
- 7 new tests: Double scalar → Float64, Boolean array → BoolArray,
  ElapsedTime scalar → Int32 (the fallback path), array-of-ElapsedTime
  rejected, InternationalizedString → String, ReferenceType rejected,
  Unknown sentinel rejected.

Test count delta: 446 -> 459 (+13; codec 215 -> 221, galaxy 49 -> 56).
All four DoD gates green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:33:42 -04:00
Joseph Doherty baea6eaa41 [M3] mxaccess-galaxy: GalaxyUserProfile + UserResolver trait + role-blob
Lands the user-resolver half of M3 stream A. Pure-Rust foundation —
the tiberius-backed SQL impl is logged as F14 and stays gated behind
the existing galaxy-resolver Cargo feature.

New
- role_blob.rs (~270 LoC, 12 tests including a garbage-between-roles
  edge case) — port of ParseRoleBlob (cs:87-133). Sliding-window scan
  over hex-decoded UTF-16LE bytes; rejects non-printable code units;
  case-insensitive dedup. Pure function, no I/O.
- user.rs (~290 LoC, 8 tests including 4 tokio-driven InMemoryUserResolver
  cases) — GalaxyUserProfile (port of cs:5-11) + from_columns helper
  bridging into role_blob + UserResolver async trait + UserResolverError
  with NotFound / Backend variants.
- sql.rs additions: USER_SELECT_SQL + USER_BY_GUID_SQL + USER_BY_NAME_SQL
  constants (port of cs:135-148). Inline concatcp! macro composes the
  base SELECT with each WHERE clause at compile time without pulling
  const_format.

Cargo.toml: added uuid (Galaxy user_guid is a uniqueidentifier).

design/followups.md: added F14 (P2) for the tiberius-backed SQL impl
behind the galaxy-resolver feature.

Test count delta: 427 -> 446 (+19; mxaccess-galaxy 30 -> 49). All four
DoD gates green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:26:24 -04:00
Joseph Doherty d84b066c62 [M3] mxaccess-galaxy: GalaxyTagMetadata + parser + Resolver trait + SQL
Lands M3 stream A — the pure-Rust foundation of the Galaxy resolver:
the data type, the tag-reference parser, the async trait, and the
canonical SQL strings. Unblocks F13 (NmxClient::write_* wrappers depend
on GalaxyTagMetadata) without pulling in tiberius yet.

New
- metadata.rs (~195 LoC, 7 tests) — GalaxyTagMetadata record (port of
  cs:6-73). Includes is_buffer_property + to_reference_handle(galaxy_id)
  bridging into mxaccess-codec::MxReferenceHandle::from_names.
- parser.rs (~330 LoC, 12 tests) — ParsedTagReference parser. Handles
  Object.Attribute (1 candidate), Object.Primitive.Attribute (2
  candidates: primitive-attr first, dotted-attr second per cs:181-185),
  and the case-insensitive .property(buffer) suffix. Pure-Rust, no I/O.
- resolver.rs (~200 LoC, 5 tests including a tokio-driven InMemoryResolver
  proving the trait is implementable without SQL) — async Resolver trait
  + ResolverError. Default browse returns Backend("not implemented") so
  read-only backends don't need to override it.
- sql.rs (~280 LoC, 5 smoke tests) — RESOLVE_SQL + BROWSE_SQL constants
  ported byte-for-byte from cs:208-432. Available publicly so any
  backend (the planned tiberius impl, a wwtools/grdb snapshot replay,
  etc.) can grab the canonical query.

Cargo.toml: added mxaccess-codec (path), async-trait, thiserror;
tokio added as dev-dependency for the resolver-trait async tests.

Deliberately deferred to a later iteration:
- The tiberius-backed Resolver impl behind the galaxy-resolver feature.
- ToValueKind / TryGetValueKind / ProjectWriteValue helpers on
  GalaxyTagMetadata (cs:41-72) — these need a MxDataType -> MxValueKind
  lookup that the codec doesn't currently expose; landing them with
  F13's write-helper iteration keeps the iteration coherent.

Test count delta: 397 -> 427 (+30). All four DoD gates green.
Open followups touched: F13 prerequisite (GalaxyTagMetadata) now in
place; F13 itself stays open until the write helpers wire it up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:17:16 -04:00
Joseph Doherty fe2a6db786 Initial project state: .NET reference, design, Rust port (M0+M1), evidence
rust / build / test / clippy / fmt (push) Has been cancelled
Layout:
- src/                    .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
                          MxAsbClient, probes, tests, harnesses. Executable spec.
- design/                 Architectural plan for the Rust port (M0–M6), error
                          model, protocol invariants, risks (R1–R16), adversarial
                          review log (review.md).
- rust/                   Rust workspace. M0 skeleton + M1 codec parity.
                          mxaccess-codec: 215 unit tests + 2 cross-implementation
                          parity tests (byte-identical against .NET reference).
                          Other crates are M0 stubs awaiting M2+.
- captures/               Frida + netsh + pcap evidence per CLAUDE.md
                          ("captures are evidence, not throwaway logs").
- analysis/               Decompiled C# (frida/proxy/decompiled-*),
                          Ghidra exports for native DLLs (`exports/` only —
                          working state at `projects/` and AVEVA's input
                          binaries at `input/` are gitignored).
- docs/                   Reverse-engineering reference docs.
- tools/                  Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
                          Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/      Rust CI: fmt + build + test + clippy on Windows.
- LICENSE                 MIT (Joseph Doherty, 2026).

Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly

Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 06:21:00 -04:00