write-commands plan: execute Phase 1 discovery (no DB writes)

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) <noreply@anthropic.com>
This commit is contained in:
dohertj2
2026-05-04 07:41:34 -04:00
parent 6888b8c55a
commit e3c003d978
@@ -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)`:
**`<Module>.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`: **`<Module>.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 <name>` (default: `RetestSdkWriteSandbox`),
`--write-value <numeric>`, `--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