Files
histsdk/docs/plans/speculative-items-sweep.md
Joseph Doherty f4709ff143 Speculative-items sweep: IntegralDivisor, cert tests, D3/D1/D2 findings
Plan: docs/plans/speculative-items-sweep.md (also covers parallelism +
findings).

Implemented:
- C3: HistorianTagDefinition.IntegralDivisor (default 1.0). Wire bytes
  flip per the captured native serializer; live probe shows the server
  stores IntegralDivisor on EngineeringUnit (shared) rather than per-tag,
  so the value is accepted on the wire but doesn't visibly persist for
  the test EU. Documented in the property's doc-comment.
- E: HistorianWcfCertOptionTests (5 tests) covering AllowUntrustedServer-
  Certificate validator installation + ServerDnsIdentity propagation
  through CreateEndpointAddress and CreateBindingPair.

Investigated + documented (deferred):
- D3: Discrete/String/Int1/Int8/UInt8 EnsT2 root cause — server-side
  ValidationFailed: "Transaction validation failed". Native AddTag's
  validator rejects non-analog types; not a wire-format issue. To unlock,
  need to capture a working native flow via a different code path
  (likely SMC's tag-import path or AddTagExtendedProperties carrying
  type-specific metadata). Defer until a customer asks.
- D1: AddTagExtendedProperties feasibility — managed surface confirmed
  (ArchestrA.HistorianAccess.AddTagExtendedProperties + WCF op
  AddTagExtendedPropertyGroups). Cost estimated at 1-2 days of focused
  RE work due to CTagExtendedPropertyGroup payload complexity. Defer.
- D2: AddRevisionValuesBegin/Value/End — sub-plan written at
  docs/plans/revision-write-path.md with 5-step capture sequence,
  workstream estimates, and risk register. Implementation deferred.

177/177 tests pass (was 172; +5 cert tests + 1 IntegralDivisor unit
test, harness probe results not committed).

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

7.0 KiB

Plan: Speculative Items Sweep (2026-05-04)

The five items I previously called out as speculative / deferred. Goal: exercise the cheap ones, investigate the medium ones to feasibility, and write sub-plans for anything too big to ship in one push.

Items

ID Item Effort Touches
C3 Expose IntegralDivisor on HistorianTagDefinition small HistorianTagDefinition.cs, HistorianTagWriteProtocol.cs, orchestrator, tests
E Unit tests for AllowUntrustedServerCertificate / ServerDnsIdentity small new test file under tests/
D3 Root-cause Discrete/String/Int1/Int8/UInt8 EnsT2 failure medium (investigation) native harness, possibly serializer
D1 Capture wire bytes for AddTagExtendedProperties medium (capture + decode) native harness, possibly new serializer + public API
D2 Implement AddRevisionValuesBegin/Value/End (revision-write path) large new orchestrator + 3 new public APIs

Parallelism

Concurrency-safe groupings (each pair is independent at the file level):

  • C3 ↔ E — C3 touches HistorianTagDefinition.cs + HistorianTagWriteProtocol.cs + orchestrator + integration tests; E adds a new test file + might add a small unit-test util. No file overlap.
  • D3 ↔ D1 — Both touch the native trace harness Program.cs, so they conflict if done concurrently. Sequence them.
  • C3/E ↔ D3/D1 — No file overlap; can run concurrently with the harness work.
  • D2 stands alone (different code paths entirely).

In a single-agent session, the order is:

  1. C3 (small, predictable) — land first
  2. E (small, predictable) — land second
  3. D3 (investigation; documents findings whether or not implementation is possible)
  4. D1 (investigation + capture; same pattern)
  5. D2 — write a focused sub-plan; do NOT implement in this sweep

Success Criteria

  • C3: HistorianTagDefinition.IntegralDivisor (default 1.0) plumbed through serializer; unit test asserts non-default value flips the wire bytes; live test asserts the value persists in Tag.IntegralDivisor (or wherever it lands in SQL).
  • E: 2-3 unit tests asserting HistorianWcfClientCredentialsHelper.Configure and HistorianWcfBindingFactory.CreateEndpointAddress honor the new options.
  • D3: documented root cause + decision (workable path / not workable / requires further capture). If a workable path emerges quickly, also implement.
  • D1: documented evidence summary + decision (worth implementing / defer / requires customer ask).
  • D2: docs/plans/revision-write-path.md (or similar) with the 5-step capture sequence + open questions.

Findings

C3 — IntegralDivisor (executed 2026-05-05)

Plumbed through serializer + orchestrator; default 1.0. Unit test verifies the 8-byte double immediately preceding the trailer flips with a non-default value. Live probe: server accepts the wire bytes but IntegralDivisor appears to be stored on EngineeringUnit (shared across all tags using that EU) rather than per-tag, and the EU's stored value didn't change for the test. Documented in the property's doc-comment. No live integration test added (nothing to assert in SQL).

E — Cert option unit tests (executed 2026-05-05)

Added HistorianWcfCertOptionTests (5 tests) covering:

  • Configure is a no-op when AllowUntrustedServerCertificate=false
  • Configure installs the accept-any validator + RevocationMode.NoCheck when the option is true
  • CreateEndpointAddress with no DNS identity returns an address with Identity == null
  • CreateEndpointAddress with a DNS identity attaches a DnsEndpointIdentity
  • CreateBindingPair(RemoteTcpCertificate) propagates ServerDnsIdentity to the History endpoint (and not to the Retrieval endpoint, which uses plain MdasNetTcp without TLS)

D3 — Discrete/String/Int1/Int8/UInt8 EnsT2 root cause (investigated 2026-05-05)

Probed each unsupported type via the native trace harness with --write-data-type {Type}. Result for SingleByteString and Int1 (others truncated in the same output):

  • HistorianAccess.AddTag returns Success=false, TagKey=0
  • Error: ErrorCode=ValidationFailed, ErrorType=CustomError, ErrorDescription="Transaction validation failed"

Conclusion: The failure is server-side, not wire-format. The /Hist.EnsT2 server-side validator rejects non-analog types when invoked through the AddTag → EnsT2 code path. To unlock these types from the SDK we'd need to:

  1. Capture a successful native creation of a discrete/string tag via some other mechanism (likely SMC's tag-import path or a different WCF op like AddTagExtendedProperties carrying the discrete/string-specific metadata)
  2. Diff the working native flow against the failing one to see what ancillary fields the validator expects (TagType vs CDataType, separate StorageType, IO-server pre-registration, etc.)

Decision: defer until a customer asks. The native AVEVA wrapper itself cannot create these tags via AddTag from a managed client — implementing this would require RE work on a path the wrapper doesn't exercise, which is much higher risk than the existing analog write surface.

D1 — AddTagExtendedProperties feasibility (investigated 2026-05-05)

Managed surface confirmed. Native API:

  • Public managed entry point: ArchestrA.HistorianAccess.AddTagExtendedProperties (token 0x0600619B, 140 IL instructions, 6 locals)
  • WCF op: CHistoryConnectionWCF.AddTagExtendedPropertyGroups (token 0x0600405C)
  • Underlying contract method: IHistoryServiceContract2.AddTagExtendedProperties (already declared in our reproduced contracts)
  • Managed input type: HistorianTagExtendedPropertyGroup wrapping the native CTagExtendedPropertyGroup C++ class. Built from a std::vector<CTagExtendedPropertyGroup> (visible in the IL locals). Property group structure not yet decoded.

Decision: defer implementation. Cost estimate:

  1. Reflect-construct HistorianTagExtendedPropertyGroup via the native harness (probably 2-4 hours — these C++/CLI types often have hidden constructor requirements that surface only at runtime).
  2. Call AddTagExtendedProperties with a sandbox group; capture wire bytes via instrument-wcf-writemessage (1 hour).
  3. Decode the CTagExtendedPropertyGroup payload — this is its own struct that needs walking field-by-field against the native serializer IL (token 0x06002038, CHistStorage.AddTagExtendedProperties) (3-6 hours).
  4. Implement managed HistorianTagExtendedPropertyGroup model + serializer
    • public AddTagExtendedPropertiesAsync API + tests (4-6 hours).

Total: 1-2 days of focused work. Defer until a customer asks for tag extended properties or the analog write surface needs them as a prerequisite.

D2 — AddRevisionValuesBegin/Value/End

Sub-plan deferred to a dedicated session — see docs/plans/revision-write-path.md (created in this sweep).

Out of scope for this sweep

Refactoring HistorianWcfTagClient to respect options.Transport for browse / metadata (i.e., let it use cert binding from Windows). Worth doing but not part of the speculative-items list.