From e3c003d978f91f5252548de0c4be2eb703e532b2 Mon Sep 17 00:00:00 2001 From: dohertj2 Date: Mon, 4 May 2026 07:41:34 -0400 Subject: [PATCH] write-commands plan: execute Phase 1 discovery (no DB writes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of docs/plans/write-commands-reverse-engineering.md is purely discovery — static IL inspection, public-API enumeration, scope elimination — and produces no DB writes. Phase 2 requires extending NativeTraceHarness with a write scenario AND explicit operator approval per the plan's safety rule §1, so it is deferred to a separate session. Phase 1 findings recorded inline in the plan's status header: 1. §3.4 ModifyData/DeleteData eliminated — no managed wrapper exists. `methods` returns zero hits for EditValue, ModifyValue, EditData, DeleteData, ModifyData, OverwriteData. Per the plan's own §3.4 disposition rule, this op is REST/SMC-only. 2. §4.a native serializer tokens identified for Phase 2: Public managed-wrapper write API in ArchestrA.HistorianAccess: - AddTag (0x0600619A) - AddStreamedValue × 3 overloads (0x0600618C/D/E) - AddNonStreamedValue × 2 overloads (0x0600618F/90) - DeleteTags (0x060061A4) - AddRevisionValuesBegin / AddRevisionValue / AddRevisionValuesEnd / AddRevisionValues (0x06006175-77, 0x0600617F) — covers the bulk-modify use case the eliminated ModifyData would have served. Worth folding into Phase 2 scope. Native serializers: - CTagUtil.ConvertTagMetadataToHistorianTag (0x060055CE) — 412 IL instructions; builds CTagMetadata for analog/discrete/string. Calls every CTagMetadata accessor we'd need to surface (Min/MaxRaw, Min/MaxEU, Unit, Message0/1, MaxLength, IntegralDivisor, DefaultTagRate, RolloverValue) plus all the CDataType predicates already decoded. - CHistoryConnectionWCF.AddStreamValuesToHistorian (0x0600404C) — confirms the on-wire shape matches our existing IHistoryServiceContract2.AddStreamValues2 declaration. 3. Chicken-and-egg resolved: the first §3.1 EnsT2 test creates the sandbox tag itself, so no SMC step is needed. 4. Open question §8.6 answered — the wrapper exposes both AddStreamedValue and AddNonStreamedValue, so the SDK should eventually surface both real-time and backfill write modes. Phase 2 next steps recorded inline in the plan as a 5-item executable checklist (extend harness with --scenario write, capture, decode EnsT2(analog) + AddS2 bytes, implement public surface, gated live tests). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../write-commands-reverse-engineering.md | 95 ++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/docs/plans/write-commands-reverse-engineering.md b/docs/plans/write-commands-reverse-engineering.md index c4bc4b4..0971377 100644 --- a/docs/plans/write-commands-reverse-engineering.md +++ b/docs/plans/write-commands-reverse-engineering.md @@ -1,7 +1,98 @@ # Plan: Reverse-Engineering Write Commands -Status: PLAN ONLY (no implementation yet). Extends the read/event -work in `docs/reverse-engineering/handoff.md` (2026-05-04). +Status: **PHASE 1 EXECUTED on 2026-05-04 — discovery complete, awaiting +operator decision on Phase 2.** No code changes; no DB writes. + +## Phase 1 findings (recorded here, not implementing) + +### §3.4 ModifyData/DeleteData — ELIMINATED FROM SCOPE + +`methods aahClientManaged.dll` returns no managed wrapper for any of: +`EditValue`, `ModifyValue`, `EditData`, `DeleteData`, `ModifyData`, +`OverwriteData`. Per the plan's §3.4 disposition rule, this op is +REST-only / SMC-only and remains out of scope for the SDK. + +### §4.a Native serializers identified (token IDs for future Phase 2) + +The wrapper does have managed-public write API: + +| Public method | Token | Used for | +|---|---|---| +| `ArchestrA.HistorianAccess.AddTag` | `0x0600619A` | Creates a new tag (drives `EnsT2`) | +| `ArchestrA.HistorianAccess.AddStreamedValue` | `0x0600618C/D/E` (3 overloads) | Pushes one timestamped value (drives `AddS2`) | +| `ArchestrA.HistorianAccess.AddNonStreamedValue` | `0x0600618F/90` (2 overloads) | Pushes one timestamped value, non-stream mode | +| `ArchestrA.HistorianAccess.DeleteTags` | `0x060061A4` | Removes tags (drives `DelT`) | +| `ArchestrA.HistorianAccess.AddVersionedStreamedValue` | `0x0600616F` | Pushes versioned value (rev edit) | +| `ArchestrA.HistorianAccess.AddRevisionValuesBegin/Value/End/AddRevisionValues` | `0x06006175-77, 0x0600617F` | Multi-row revision write (replaces `ModifyData` use case) | + +So even though the engine doesn't expose `ModifyData` over WCF, the +**revision-write path** (`AddRevisionValuesBegin → AddRevisionValue * N → +AddRevisionValuesEnd`) covers the bulk-modify use case. This is a NEW +discovery worth folding into the Phase 2 scope. + +Native serializer for `EnsT2(analog/discrete/string)`: +**`.CTagUtil.ConvertTagMetadataToHistorianTag`** at token +`0x060055CE` (412 IL instructions, 10 locals). Calls `CTagMetadata.GetUnit`, +`GetMessage0`, `GetMessage1`, `GetMaxLength`, `GetMinRaw`, `GetMaxRaw`, +`GetMinEU`, `GetMaxEU`, `GetIntegralDivisor`, `GetDefaultTagRate`, +`GetRolloverValue`, plus `CDataType.IsAnalog/IsWideString/GetRawType/ +GetTagType` — i.e. every field the analog `CTagMetadata` shape would +need is wired through this method. Decoding it line-by-line **OR** +capturing live wire bytes against a sandbox tag are the two ways +forward. + +WCF wrapper for `AddS2`: **`.CHistoryConnectionWCF.AddStreamValuesToHistorian`** +at token `0x0600404C`. Confirms the on-wire shape is +`IHistoryServiceContract2.AddStreamValues2(string handle, byte[] pBuf, +out byte[] errorBuffer)` — matches our existing contract. Handle is +the same Open2 v6 session GUID we already extract. + +### Phase 2 chicken-and-egg resolved + +Per §5 ordering: §3.1 (EnsT2) must come before §3.2 (AddS2) because +AddS2 needs an existing tag. The sandbox tag itself is created BY +the first §3.1 EnsT2 test. So the very first write-flow run creates +`RetestSdkWriteSandbox`. No SMC required — the chain is closed. + +### Open question (was §8.6) answered + +The wrapper exposes `AddStreamedValue` AND `AddNonStreamedValue`. +The latter is the documented path for backfilling values older than +`RealTimeWindow`. So the SDK should expose both modes, not just +`AddStreamedValue`. Update the success criteria for `AddS2` +accordingly. + +### Phase 2 next steps (NOT EXECUTED in this session) + +1. Extend `tools/AVEVA.Historian.NativeTraceHarness/Program.cs` with a + `--scenario write` that calls `HistorianAccess.AddTag` (creating + `RetestSdkWriteSandbox` if absent) followed by + `HistorianAccess.AddStreamedValue`. New args: + `--write-sandbox-tag ` (default: `RetestSdkWriteSandbox`), + `--write-value `, `--write-data-type analog|discrete|string`. +2. Run the harness with `instrument-wcf-writemessage` + + `instrument-wcf-readmessage` instrumented copies of + `aahClientManaged.dll` to capture the full write flow. +3. Decode `EnsT2(analog)` `InBuff` bytes against the IL of + `CTagUtil.ConvertTagMetadataToHistorianTag` (token `0x060055CE`). +4. Decode `AddS2` `pBuf` bytes against the IL of + `CHistoryConnectionWCF.AddStreamValuesToHistorian` (token + `0x0600404C`). +5. Implement `WriteValueAsync`, `EnsureTagAsync`, `DeleteTagAsync` per + §4.e of the original plan; live tests gated by + `HISTORIAN_WRITE_SANDBOX_TAG`. + +Phase 2 was deferred because (a) it requires extending the harness +(non-trivial scaffolding) and (b) per safety §1, even sandbox-tag +writes warrant explicit operator approval before the first run. The +operator decides whether to proceed; if yes, the instructions above +are executable as-is. + +--- + +Original plan content below. + + ## 1. Goal