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>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Plan: Revision-Write Path (`AddRevisionValuesBegin/Value/End`)
|
||||
|
||||
Status: **NOT STARTED.** Sub-plan extracted from `speculative-items-sweep.md`
|
||||
item D2 because the work is too large for a one-push sweep.
|
||||
|
||||
## Context
|
||||
|
||||
The Historian's "revision write" path is the documented mechanism for
|
||||
editing historized data after the fact (replaces the inferred
|
||||
`ModifyData` / `DeleteData` use cases that don't exist as WCF ops). Native
|
||||
managed surface (per Phase 1 findings of the write-commands plan):
|
||||
|
||||
| Public method | Token | Purpose |
|
||||
|---|---|---|
|
||||
| `ArchestrA.HistorianAccess.AddRevisionValuesBegin` | `0x06006175` | Open a revision-edit transaction |
|
||||
| `ArchestrA.HistorianAccess.AddRevisionValue` | `0x06006176` | Append a value to the open transaction |
|
||||
| `ArchestrA.HistorianAccess.AddRevisionValuesEnd` | `0x06006177` | Commit the transaction |
|
||||
| `ArchestrA.HistorianAccess.AddRevisionValues` | `0x0600617F` | Single-shot variant |
|
||||
| `ArchestrA.HistorianAccess.AddVersionedStreamedValue` | `0x0600616F` | Push one versioned value (related path) |
|
||||
|
||||
WCF surface is unknown — likely a new op group on `IHistoryServiceContract2`
|
||||
or `IRetrievalServiceContract4` or a new contract.
|
||||
|
||||
## Goal
|
||||
|
||||
Public SDK API:
|
||||
|
||||
```csharp
|
||||
public Task<HistorianRevisionTransaction> BeginRevisionAsync(string tag, CancellationToken ct);
|
||||
// On the returned transaction:
|
||||
public Task AddRevisionValueAsync(HistorianSampleEdit sample, CancellationToken ct);
|
||||
public Task<bool> CommitAsync(CancellationToken ct);
|
||||
// IDisposable / IAsyncDisposable for cancellation rollback if such a thing exists
|
||||
```
|
||||
|
||||
Or a single batch convenience:
|
||||
|
||||
```csharp
|
||||
public Task<bool> WriteRevisionsAsync(string tag, IReadOnlyList<HistorianSampleEdit> samples, CancellationToken ct);
|
||||
```
|
||||
|
||||
The choice depends on the wire shape — if Begin/Value/End requires the
|
||||
caller to maintain a server handle between calls, the disposable
|
||||
transaction is necessary; if it's stateless, the batch convenience is fine.
|
||||
|
||||
## Workstreams
|
||||
|
||||
### A. Static analysis (1-2 hours)
|
||||
|
||||
- Inspect IL for the four managed public methods to identify the
|
||||
underlying `CHistoryConnectionWCF.*` calls and their server-side WCF
|
||||
contract methods.
|
||||
- Add the contract methods to `Wcf/Contracts/IHistoryServiceContract2.cs`
|
||||
(or a new contract if appropriate) with `[OperationContract(Name = "...")]`
|
||||
+ `[MessageParameter]` attributes once names are known.
|
||||
|
||||
### B. Native harness extension (2-3 hours)
|
||||
|
||||
- Add `--scenario revision-write` to the harness.
|
||||
- Refer to existing `--scenario write` plumbing for the AddTag wrapper
|
||||
pattern.
|
||||
- Sequence:
|
||||
1. Open connection (probably write-enabled mode `0x401`)
|
||||
2. AddTag for sandbox tag (re-uses existing harness flow)
|
||||
3. AddStreamedValue for the initial sample (currently blocked
|
||||
architecturally per Phase 2 findings — but may not be required if
|
||||
the revision path operates directly on the historian engine state)
|
||||
4. AddRevisionValuesBegin / AddRevisionValue × N / AddRevisionValuesEnd
|
||||
5. Read back via existing read path; verify the samples reflect the
|
||||
edits
|
||||
|
||||
### C. Wire capture (1 hour)
|
||||
|
||||
- Same `instrument-wcf-writemessage` + `instrument-wcf-readmessage`
|
||||
IL-rewrite tooling already used for EnsT2 / DelT.
|
||||
- Capture both Begin/Value/End and the single-shot AddRevisionValues
|
||||
variant for byte-level diff.
|
||||
|
||||
### D. Decode + managed serializer (4-6 hours)
|
||||
|
||||
- Walk the captured InBuff bytes against the native serializer IL.
|
||||
- The Begin payload likely seeds a server-side transaction handle that
|
||||
Value calls reference. Look for an `out`-returned handle in the Begin
|
||||
response.
|
||||
- Value payload structure is likely similar to `AddS2`'s pBuf
|
||||
(uint16 version + uint32 sampleCount + N × {tagId, FILETIME, quality,
|
||||
typed value bytes}) but may include a per-sample revision/version field.
|
||||
|
||||
### E. Public API + tests (4-6 hours)
|
||||
|
||||
- New types: `HistorianSampleEdit` (sample + reason/version metadata),
|
||||
`HistorianRevisionTransaction` (disposable handle).
|
||||
- Public methods on `HistorianClient` per the Goal section.
|
||||
- Unit tests: golden-byte fixtures for Begin/Value/End/Commit payloads.
|
||||
- Live integration tests: write a known sample, edit it via the
|
||||
revision path, read back and assert the new value appears.
|
||||
|
||||
## Risks
|
||||
|
||||
- **Server-cache prerequisite.** If the historian's revision path
|
||||
also requires the tag to be "live in the runtime cache" (the same
|
||||
blocker that killed `AddS2`), the entire path may be unimplementable
|
||||
for the same architectural reason.
|
||||
- **State across calls.** Begin/Value/End may store transaction state
|
||||
on the server keyed by the WCF session GUID. WCF's session model
|
||||
needs to be configured to keep the same channel alive across all
|
||||
three calls — which is a different lifecycle from the existing
|
||||
one-call-per-channel pattern in the SDK orchestrators.
|
||||
- **Concurrent edits.** Server may reject concurrent revision
|
||||
transactions on the same tag — needs probing.
|
||||
- **Time bounds.** Revision likely respects the same `RealTimeWindow`
|
||||
/ `FutureTimeThreshold` system parameters as `AddS2`. Out-of-window
|
||||
edits silently drop or error — needs probing.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Public `BeginRevisionAsync` (or batch variant) live-verified against
|
||||
a sandbox tag created by `EnsureTagAsync`.
|
||||
- Round-trip test: write initial value → revise it → read back → verify
|
||||
the revised value persists in `History` extension table via SQL.
|
||||
- Golden-byte fixtures for Begin / Value / End / Commit captured against
|
||||
the sandbox tag.
|
||||
- Decision documented for whether the `AddRevisionValues` single-shot
|
||||
variant is exposed in addition to the Begin/Value/End sequence.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Existing analog write surface (`EnsureTagAsync`) — done.
|
||||
- `AddS2` is **not** a prerequisite; the revision path may be an
|
||||
independent code path that bypasses the runtime-cache gate. If it
|
||||
doesn't, this plan is blocked the same way `AddS2` is.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Editing event tags. Events come from AVEVA AnE; the SDK only reads
|
||||
them.
|
||||
- Bulk schema changes. Forbidden over the wire per the Historian's
|
||||
architecture.
|
||||
|
||||
## Trigger to start
|
||||
|
||||
A customer-driven request, or a real need to expose historical data
|
||||
correction in the SDK's API. Without one, this remains the most
|
||||
substantive remaining write-path workstream but isn't worth the 1-2
|
||||
days of focused work speculatively.
|
||||
Reference in New Issue
Block a user