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