CW-1: reusable capture -> sanitize -> golden-fixture pipeline

Adds the highest-leverage reverse-engineering primitive from the roadmap: one
path to turn a live operation buffer into a committable golden fixture. Unblocks
every capture-tier item (R0.5, R1.x, R2.1).

- ProtocolCaptureSanitizer: redacts identity-bearing values (host, tag, user,
  machine) from a native buffer in BOTH ASCII and UTF-16LE, overwriting in place
  with an 'X' fill so length and every field offset are preserved (keeps the
  fixture useful for byte-layout RE). ASCII-letter matching is case-insensitive;
  secrets < 3 chars are skipped to avoid collision corruption. AssertNoSecretsRemain
  is a fail-closed safety net that refuses to emit if any value survives.
- ProtocolFixtureWriter: serializes a capture to fixtures/protocol/<op>/<name>.json
  with sanitized hex, length, SHA-256 of the sanitized bytes, and a scrub report.
  Timestamps are passed in (deterministic / testable).
- capture-tag-info CLI command: captures a live GetTagInfoFromName response and
  writes the fixture. The same native bytes ride inside 2023 R2 gRPC
  GetTagInfosFromName, so the fixture is transport-agnostic.
- 11 unit tests for the sanitizer/writer (test project now references the RE tool).
- First real fixture: get-tag-info/analog-*.json — a 98-byte Int4 CTagMetadata
  buffer captured live from the local Historian 2020 server, tag name redacted,
  verified to contain no identity (descriptor 03 c3 00 31 = Int4, as documented).

180 non-live unit tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-06-19 14:56:48 -04:00
parent 6b892b69ba
commit fa9cde3e2f
6 changed files with 502 additions and 0 deletions
@@ -0,0 +1,18 @@
{
"op": "get-tag-info",
"capturedUtc": "2026-06-19T18:55:46.5988258Z",
"notes": "RetrievalService.GetTagInfoFromName response (CTagMetadata buffer); identical bytes on 2023 R2 gRPC GetTagInfosFromName.",
"request": null,
"response": {
"length": 98,
"sha256": "cdda36baa869355b52ccb4be2735ccacfa2da69f0cafe62e88b807f1a05089fd",
"hex": "03c3003184228c4058e1874a984b3dbecbe0aa42ee000000091d0058585858585858585858585858585858585858585858585858585858580904004d44415302030102000000d057f49465d8dc010a0000000000000024400000000000002440fe00",
"redactions": [
{
"secret": "tag",
"asciiMatches": 1,
"utf16Matches": 0
}
]
}
}