28 KiB
SuiteLink Tag Client Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Build a cross-platform .NET 10 C# SuiteLink V2 client for tag-only operations: connect, subscribe, receive updates, write values, and unsubscribe for bool, int32, float32, and string.
Architecture: The implementation is split into transport, protocol codec, session state, and public client API layers. The first version targets normal non-encrypted SuiteLink tag traffic only and validates behavior with unit tests, golden packet tests, and optional live integration tests against an AVEVA/OI server in mixed or legacy mode.
Tech Stack: .NET 10, C#, xUnit, TcpClient/NetworkStream, System.Buffers, System.Buffers.Binary
Task 1: Create Solution Skeleton
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLink.Client.csproj - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Class1.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLink.Client.Tests.csproj - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/UnitTest1.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
Step 1: Write the failing structure check
Create a test project with one placeholder test:
using Xunit;
namespace SuiteLink.Client.Tests;
public sealed class UnitTest1
{
[Fact]
public void Placeholder()
{
Assert.True(true);
}
}
Step 2: Run test to verify the solution builds
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
Expected: PASS with one test executed
Step 3: Write minimal implementation
Create the library and solution structure only. Leave the default library type empty or minimal.
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln /Users/dohertj2/Desktop/suitelinkclient/src /Users/dohertj2/Desktop/suitelinkclient/tests
git commit -m "chore: scaffold suitelink client solution"
Task 2: Define Public Value And Option Models
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkConnectionOptions.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkValue.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkTagUpdate.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SubscriptionHandle.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkValueTests.cs
Step 1: Write the failing test
using Xunit;
namespace SuiteLink.Client.Tests;
public sealed class SuiteLinkValueTests
{
[Fact]
public void BoolFactory_CreatesBoolValue()
{
var value = SuiteLinkValue.FromBoolean(true);
Assert.True(value.TryGetBoolean(out var result));
Assert.True(result);
}
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter BoolFactory_CreatesBoolValue
Expected: FAIL with missing type or method errors
Step 3: Write minimal implementation
Implement:
- immutable
SuiteLinkConnectionOptions SuiteLinkValuediscriminated wrapper forbool,int,float,stringSuiteLinkTagUpdateSubscriptionHandleplaceholder with async disposal hook
Minimal SuiteLinkValue pattern:
public enum SuiteLinkValueKind
{
Boolean,
Int32,
Float32,
String
}
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkValueTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkValueTests.cs
git commit -m "feat: add public suitelink value models"
Task 3: Add Frame Reader And Writer
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkFrame.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkFrameWriter.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkFrameReader.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkFrameWriterTests.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkFrameReaderTests.cs
Step 1: Write the failing test
using Xunit;
namespace SuiteLink.Client.Tests.Protocol;
public sealed class SuiteLinkFrameWriterTests
{
[Fact]
public void WriteFrame_AppendsLengthTypeAndMarker()
{
var bytes = SuiteLinkFrameWriter.WriteFrame(0x2440, []);
Assert.Equal(5, bytes.Length);
Assert.Equal(0x03, bytes[0]);
Assert.Equal(0x00, bytes[1]);
Assert.Equal(0x40, bytes[2]);
Assert.Equal(0x24, bytes[3]);
Assert.Equal(0xA5, bytes[4]);
}
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter WriteFrame_AppendsLengthTypeAndMarker
Expected: FAIL with missing frame writer
Step 3: Write minimal implementation
Implement:
SuiteLinkFramerecord holding message type and payload span or byte array- frame writer for normal SuiteLink messages
- frame reader that:
- reads two-byte remaining length
- reads remaining bytes plus trailing marker
- validates final
0xA5
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkFrame
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol
git commit -m "feat: add suitelink frame reader and writer"
Task 4: Add UTF-16LE And Primitive Wire Encoding Helpers
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkEncoding.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkEncodingTests.cs
Step 1: Write the failing test
using Xunit;
namespace SuiteLink.Client.Tests.Protocol;
public sealed class SuiteLinkEncodingTests
{
[Fact]
public void EncodeLengthPrefixedUtf16_WritesCharacterCountThenUtf16Bytes()
{
var bytes = SuiteLinkEncoding.EncodeLengthPrefixedUtf16("AB");
Assert.Equal(1 + 4, bytes.Length);
Assert.Equal(2, bytes[0]);
Assert.Equal((byte)'A', bytes[1]);
}
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeLengthPrefixedUtf16_WritesCharacterCountThenUtf16Bytes
Expected: FAIL
Step 3: Write minimal implementation
Implement helper methods for:
- one-byte-length-prefixed UTF-16LE strings
- null-terminated UTF-16LE strings
- little-endian primitive reads/writes
- FILETIME conversion helper if needed for future time messages
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkEncodingTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkEncoding.cs /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkEncodingTests.cs
git commit -m "feat: add suitelink encoding helpers"
Task 5: Encode Handshake And Connect Messages
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkHandshakeCodec.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkConnectCodec.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkHandshakeCodecTests.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkConnectCodecTests.cs
Step 1: Write the failing test
Use known bytes captured from the reverse-engineered protocol and assert:
[Fact]
public void EncodeConnect_WritesConnectMessageType()
{
var options = new SuiteLinkConnectionOptions(
host: "127.0.0.1",
application: "App",
topic: "Topic",
clientName: "Client",
clientNode: "Node",
userName: "User",
serverNode: "Server");
var bytes = SuiteLinkConnectCodec.Encode(options);
Assert.Equal(0x80, bytes[2]);
Assert.Equal(0x01, bytes[3]);
Assert.Equal(0xA5, bytes[^1]);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeConnect_WritesConnectMessageType
Expected: FAIL
Step 3: Write minimal implementation
Implement:
- normal SuiteLink handshake encoder
- handshake acknowledgement parser
- connect encoder using the observed field order
- isolate unknown fixed bytes in constants with comments pointing to capture evidence
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter CodecTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol
git commit -m "feat: encode suitelink handshake and connect"
Task 6: Encode Advise And Unadvise Messages
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkSubscriptionCodec.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkSubscriptionCodecTests.cs
Step 1: Write the failing test
[Fact]
public void EncodeAdvise_WritesTagNameInUtf16()
{
var bytes = SuiteLinkSubscriptionCodec.EncodeAdvise("Pump001.Run");
Assert.Equal(0x80, bytes[2]);
Assert.Equal(0x10, bytes[3]);
Assert.Equal(0xA5, bytes[^1]);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeAdvise_WritesTagNameInUtf16
Expected: FAIL
Step 3: Write minimal implementation
Implement:
EncodeAdvise(string itemName)EncodeUnadvise(uint tagId)DecodeAdviseAck(ReadOnlySpan<byte>)
Add a small result model for advise acknowledgements that captures tagId.
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkSubscriptionCodecTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkSubscriptionCodecTests.cs
git commit -m "feat: add advise and unadvise codec support"
Task 7: Decode Update Messages For Primitive Types
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkUpdateCodec.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkWireValueType.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkUpdateCodecTests.cs
Step 1: Write the failing test
[Fact]
public void DecodeUpdate_DecodesIntegerValue()
{
var frame = new byte[]
{
0x0D, 0x00, 0x00, 0x09,
0x34, 0x12, 0x00, 0x00,
0x01, 0x00,
0xC0, 0x00,
0x02,
0x2A, 0x00, 0x00, 0x00,
0xA5
};
var update = SuiteLinkUpdateCodec.Decode(frame);
Assert.Equal(0x1234u, update.TagId);
Assert.True(update.Value.TryGetInt32(out var value));
Assert.Equal(42, value);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter DecodeUpdate_DecodesIntegerValue
Expected: FAIL
Step 3: Write minimal implementation
Implement decoding for:
- binary to
bool - integer to
int - real to
float - message to
string
Return a parsed update model containing:
TagIdQualityElapsedMillisecondsSuiteLinkValue
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkUpdateCodecTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkUpdateCodecTests.cs
git commit -m "feat: decode primitive suitelink update values"
Task 8: Encode Poke Messages For Primitive Writes
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol/SuiteLinkWriteCodec.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkWriteCodecTests.cs
Step 1: Write the failing test
[Fact]
public void EncodeWrite_Int32Value_WritesPokeMessage()
{
var bytes = SuiteLinkWriteCodec.Encode(0x1234, SuiteLinkValue.FromInt32(42));
Assert.Equal(0x08, bytes[2]);
Assert.Equal(0x0B, bytes[3]);
Assert.Equal(0xA5, bytes[^1]);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter EncodeWrite_Int32Value_WritesPokeMessage
Expected: FAIL
Step 3: Write minimal implementation
Implement primitive write encoding for:
boolint32float32stringif confirmed by packet format used for wire message values
Reject unsupported value kinds with a clear exception.
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkWriteCodecTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Protocol /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/SuiteLinkWriteCodecTests.cs
git commit -m "feat: encode primitive suitelink writes"
Task 9: Implement Session State And Tag Mapping
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSessionState.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSession.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Internal/SuiteLinkSessionTests.cs
Step 1: Write the failing test
[Fact]
public void RegisterSubscription_MapsItemNameToTagId()
{
var session = new SuiteLinkSession();
session.RegisterSubscription("Pump001.Run", 0x1234);
Assert.Equal(0x1234u, session.GetTagId("Pump001.Run"));
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter RegisterSubscription_MapsItemNameToTagId
Expected: FAIL
Step 3: Write minimal implementation
Implement:
- session state enum
- tag map for
itemName -> tagId - reverse lookup for
tagId -> itemName - subscription callback registration
- update dispatch helper
Keep transport mocking simple. Do not implement full socket I/O in this task.
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkSessionTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Internal/SuiteLinkSessionTests.cs
git commit -m "feat: add session state and tag mapping"
Task 10: Implement Tcp Transport Loop
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Transport/SuiteLinkTcpTransport.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Transport/ISuiteLinkTransport.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Transport/SuiteLinkTcpTransportTests.cs
Step 1: Write the failing test
[Fact]
public async Task SendAsync_WritesFrameToUnderlyingStream()
{
var stream = new MemoryStream();
var transport = new SuiteLinkTcpTransport(stream);
await transport.SendAsync(new byte[] { 0x01, 0x02 }, CancellationToken.None);
Assert.Equal(new byte[] { 0x01, 0x02 }, stream.ToArray());
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SendAsync_WritesFrameToUnderlyingStream
Expected: FAIL
Step 3: Write minimal implementation
Implement:
- transport abstraction
- stream-backed send and receive methods
- real
TcpClientconstructor path - test-friendly stream injection path
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkTcpTransportTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Transport /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Transport/SuiteLinkTcpTransportTests.cs
git commit -m "feat: add suitelink transport abstraction"
Task 11: Implement Public Client Connect And Disconnect
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs - Modify:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SubscriptionHandle.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientConnectionTests.cs
Step 1: Write the failing test
[Fact]
public async Task ConnectAsync_TransitionsClientToConnectedState()
{
var client = new SuiteLinkClient(new FakeTransport());
await client.ConnectAsync(TestOptions.Create());
Assert.True(client.IsConnected);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter ConnectAsync_TransitionsClientToConnectedState
Expected: FAIL
Step 3: Write minimal implementation
Implement:
SuiteLinkClient- connect flow invoking handshake then connect codec
- disconnect path
- basic disposal
Expose a minimal IsConnected property or equivalent state for tests.
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkClientConnectionTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SubscriptionHandle.cs /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientConnectionTests.cs
git commit -m "feat: implement suitelink client connection flow"
Task 12: Implement Subscribe And Read Flow
Files:
- Modify:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs - Modify:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSession.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientSubscriptionTests.cs
Step 1: Write the failing test
[Fact]
public async Task ReadAsync_ReturnsFirstUpdateForRequestedTag()
{
var client = TestClientFactory.CreateConnectedClientWithUpdate("Pump001.Run", SuiteLinkValue.FromBoolean(true));
var update = await client.ReadAsync("Pump001.Run", TimeSpan.FromSeconds(1));
Assert.True(update.Value.TryGetBoolean(out var value));
Assert.True(value);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter ReadAsync_ReturnsFirstUpdateForRequestedTag
Expected: FAIL
Step 3: Write minimal implementation
Implement:
SubscribeAsyncReadAsyncvia temporary subscription- callback dispatch on incoming updates
- unadvise when subscription handle is disposed
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkClientSubscriptionTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientSubscriptionTests.cs
git commit -m "feat: implement suitelink subscribe and read flow"
Task 13: Implement Primitive Write Flow
Files:
- Modify:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/SuiteLinkClient.cs - Modify:
/Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client/Internal/SuiteLinkSession.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientWriteTests.cs
Step 1: Write the failing test
[Fact]
public async Task WriteAsync_SendsPokeForSubscribedTag()
{
var client = TestClientFactory.CreateConnectedSubscribedClient("Pump001.Speed", 0x1234);
await client.WriteAsync("Pump001.Speed", SuiteLinkValue.FromInt32(42));
Assert.Contains(client.SentFrames, frame => frame[2] == 0x08 && frame[3] == 0x0B);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter WriteAsync_SendsPokeForSubscribedTag
Expected: FAIL
Step 3: Write minimal implementation
Implement:
WriteAsync- tag lookup by item name
- fail if item is unknown or not yet subscribed
- use primitive write codec
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter SuiteLinkClientWriteTests
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/src/SuiteLink.Client /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/SuiteLinkClientWriteTests.cs
git commit -m "feat: implement primitive suitelink writes"
Task 14: Add Golden Packet Fixtures
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Fixtures/README.md - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Fixtures/*.bin - Modify:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol/*Tests.cs
Step 1: Write the failing test
Add one fixture-backed assertion that compares encoded output to a stored handshake or connect frame.
[Fact]
public void EncodeConnect_MatchesGoldenFixture()
{
var expected = File.ReadAllBytes("Fixtures/connect.bin");
var actual = SuiteLinkConnectCodec.Encode(TestOptions.Create()).ToArray();
Assert.Equal(expected, actual);
}
Step 2: Run test to verify it fails
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter MatchesGoldenFixture
Expected: FAIL until fixture and codec match
Step 3: Write minimal implementation
Add fixture files and normalize tests to read them from disk. Document where the fixture bytes came from and which captures or protocol references justify them.
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter Fixture
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Fixtures /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.Tests/Protocol
git commit -m "test: add golden packet fixtures for suitelink codec"
Task 15: Add Live Integration Test Harness
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/SuiteLink.Client.IntegrationTests.csproj - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/IntegrationSettings.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/TagRoundTripTests.cs - Create:
/Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests/README.md - Modify:
/Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
Step 1: Write the failing test
[Fact(Skip = "Requires live AVEVA SuiteLink endpoint")]
public async Task CanSubscribeAndWriteBooleanTag()
{
var client = new SuiteLinkClient();
await client.ConnectAsync(IntegrationSettings.Load());
}
Step 2: Run test to verify the harness builds
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln --filter CanSubscribeAndWriteBooleanTag
Expected: PASS or SKIP with the test discovered
Step 3: Write minimal implementation
Add:
- integration project
- environment-based settings loader
- skipped or conditional tests for boolean, integer, float, and string tags
Step 4: Run test to verify it passes
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
Expected: PASS with integration tests skipped by default
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/tests/SuiteLink.Client.IntegrationTests /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
git commit -m "test: add suitelink integration test harness"
Task 16: Add Package Documentation
Files:
- Create:
/Users/dohertj2/Desktop/suitelinkclient/README.md - Modify:
/Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-design.md
Step 1: Write the failing documentation check
Define the required README sections:
- project purpose
- supported protocol scope
- supported types
- unsupported features
- local build and test commands
- integration test setup
Step 2: Run documentation review
Run: rg -n "Supported|Unsupported|Build|Test|Integration" /Users/dohertj2/Desktop/suitelinkclient/README.md
Expected: FAIL until README exists
Step 3: Write minimal implementation
Create a README that states:
- v1 supports normal SuiteLink V2 tag operations only
- v1 does not support AlarmMgr or secure V3
- primitive types only
- exact
dotnetcommands for build and test
Step 4: Run documentation review
Run: rg -n "Supported|Unsupported|Build|Test|Integration" /Users/dohertj2/Desktop/suitelinkclient/README.md
Expected: PASS
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/README.md /Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-design.md
git commit -m "docs: describe suitelink client scope and usage"
Task 17: Full Verification Pass
Files:
- Modify:
/Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-implementation-plan.md
Step 1: Run unit and integration-default test suite
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln
Expected: PASS with integration tests skipped by default
Step 2: Run build verification
Run: dotnet build /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln -c Release
Expected: PASS
Step 3: Run formatting or analyzer checks if added
Run: dotnet test /Users/dohertj2/Desktop/suitelinkclient/SuiteLink.Client.sln -c Release
Expected: PASS
Step 4: Update plan status notes if execution deviated
Add a short note to the plan if any task required deviation due to verified protocol differences.
Step 5: Commit
git add /Users/dohertj2/Desktop/suitelinkclient/docs/plans/2026-03-16-suitelink-client-implementation-plan.md
git commit -m "docs: finalize suitelink implementation verification"