Repository navigation
SDK.Http: points, bulk points, run progress, history, delete etag, station-unreachable (epic-128 feature-001) - #31
Conversation
…ry, delete with etag (epic-128 feature-001) design.md §1.1, §2.6-§2.7, §3: ProgramPoint/PointWrite/PointEditResult/PointsEditResult/RingStartResult/ PointRefusal/PointsRefused/PointsRefusedException; BlockRunState/BlockProgress/RunProgress; HistoryCommit/ HistoryLog. IProgramsApi gains GetPoints/GetPoint/PutPoint/PutPoints/CalculateRingStart, GetRunProgress, WaitForRun (1 s client-side poll, 1-600 s), GetHistory and DeleteAsync(id, etag); IRepositoriesApi gains GetHistory. Tests F001-U-001 to F001-U-012. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…epic-128 feature-001)
design.md §4, ADR-13: StationUnreachableHandler maps a connection or name-resolution failure to
StationUnreachableException ("... at {base}: {reason}. Nothing was done.") and no answer within the
timeout to "... may or may not have been applied". The handler owns the configured HttpClient.Timeout
(the client's own becomes infinite) because a DelegatingHandler cannot tell HttpClient's timeout from the
caller's cancellation; a caller's cancellation stays an OperationCanceledException. Registering twice
shares one handler. Tests F001-U-013, F001-U-014.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rafalmaciag
left a comment
There was a problem hiding this comment.
dotnet-reviewer (epic-128 feature-001), verdict APPROVE-WITH-FIXES. Tests run locally: RocketWelder.SDK.Http.Tests 152 passed / 0 failed / 0 skipped.
Spec alignment is complete:
- Every method and DTO of design §1.1, §2.6, §2.7, §3 and §4 is present under the exact name.
- Wire shape: camelCase,
jointsa number array, nullPointWritemembers omitted via attributes, enum names forBlockRunState/RunProgress.State. - The wait refuses timeouts outside 1–600 s before sending anything.
DeleteAsync(id, etag)tells the etag 409 from the running 409.- Boundaries: no new PackageReference or ProjectReference; SDK.Http still references no rw2 assembly (context-map E1/E2).
- Timeout deviation, verified by reading the code:
- the body is buffered inside the timer;
- a caller's cancellation still surfaces as OperationCanceledException;
- the guard against a second registration holds;
- the response is disposed on every throw path.
One MAJOR is a test gap on the body-stall bound. The rest are MINOR or NIT; see the line comments.
Not in the diff (follow-ups):
csharp/Directory.Build.propsfallback2.28.0is below the published 3.1.1, contrary to its own comment. The preview and the release are unaffected becausepreview-publish.yml:92passes/p:Version. It is a release-time bump, not this PR.- The
RobotPoseXML doc says meters/radians, while the wire is mm/deg (pre-existing). - design.md §4 and §2.6 need doc-keeper updates for deviations 1 and 4.
Full report: docs epics/epic-128-customer-part-twin-test/features/feature-001-rw2-mcp-write-side/review-sdk-pr31.md.
…e-001) - F-1: test that a response whose headers arrive but whose body stalls is cut by the handler's timer (StationUnreachableException, MayHaveBeenApplied). Mutation-checked: without LoadIntoBufferAsync it fails. - F-2/F-3: WaitForRunAsync runs on an injected TimeProvider (internal ProgramsApi ctor parameter, TimeProvider.System by default). F001-U-007/U-008 assert the 1 s poll spacing and the return at the deadline on a manual clock, with no real sleeps. - F-7: every poll after the first is cut to the time left; a cut poll returns the last answer. - F-4: StationUnreachableHandler and its constructor are internal (InternalsVisibleTo for the tests). - F-6: the RunProgress.ArcOnSeconds doc states ADR-8. - F-8: the handler settings are one immutable StationEndpoint swapped by reference. - F-9: the Directory.Build.props fallback version is 3.2.0 (lead ruling on F-5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rafalmaciag
left a comment
There was a problem hiding this comment.
dotnet-reviewer re-check of 6112f25: APPROVE. Posted as COMMENT because GitHub refuses an approval from the PR author's own account.
Tests run locally: RocketWelder.SDK.Http.Tests 155 passed / 0 failed / 0 skipped.
Every finding is resolved and its thread closed:
- F-1: the body-stall test is load-bearing. I mutation-checked it in a scratch copy: with LoadIntoBufferAsync removed, it fails.
- F-2/F-3: the wait runs on an injected TimeProvider. The tests drive a manual clock, assert poll spacing of 0/1/2 s, and use no real sleeps.
- F-4: the handler is internal, and no public member of v3.1.1 was removed.
- F-5: the release is 3.2.0, by the lead's ruling.
- F-6: the ArcOnSeconds doc now states ADR-8.
- F-7: every poll after the first is cut to the time left. This is acceptable; one note is left on the thread, with no change asked.
- F-8: the handler reads one immutable StationEndpoint, swapped by reference.
- F-9: the fallback version is 3.2.0.
Doc follow-up for the doc-keeper, not this PR: design.md §4 and §2.6 still describe the pre-deviation shape (F-11).
SDK.Http side of epic-128 feature-001 "rw2 MCP write side" (
docs/epics/epic-128-customer-part-twin-test/features/feature-001-rw2-mcp-write-side/design.md). rw2 will return these DTOs (ADR-1), so it pins the preview below.What changed, by design section
§1.1 Points
Programs/):PointAxis,ProgramPoint,PointWrite(optional members carry[JsonIgnore(WhenWritingNull)], so a field left out stays off the wire under any serializer options),PointEditResult,PointsEditResult,RingStartResult,PointRefusal,PointsRefused,PointsRefusedException.IProgramsApi.GetPointsAsync,GetPointAsync,PutPointAsync,PutPointsAsync,CalculateRingStartAsync. Errors: 400/422 →ValueRefusedException; a bulk 422 →PointsRefusedException, listing every name and reason; 409 →ProgramEtagMismatchException; 404 on calculate-start →BlockNotFoundException; anything else →HttpRequestExceptionwith the status and rw2's sentence.§2.6–§2.7 Run progress
BlockRunState(string enum),BlockProgress,RunProgress.GetRunProgressAsync, andWaitForRunAsync: a client-side poll every 1 s. The timeout must be 1–600 s; outside that range it throwsArgumentOutOfRangeExceptionbefore any request is sent. It returns the first answer that is notRunning, or the last answer when the timeout passes.§3 Delete and history
DeleteAsync(programId, etag)sendsIf-Match. A 409 whose body has anetagproperty →ProgramEtagMismatchException; any other 409 →ProgramRunningException.HistoryCommit,HistoryLog;Programs.GetHistoryAsync(id, limit?)andRepositories.GetHistoryAsync(id, limit?).§4 Station unreachable (ADR-13)
StationUnreachableHandler, registered byAddRocketWelderClient, andStationUnreachableException : HttpRequestException(Station,MayHaveBeenApplied).HttpClient.Timeout, and the client's own timeout becomes infinite. ADelegatingHandlergets one token that merges HttpClient's timeout with the caller's token, so it cannot tell the two apart. The handler buffers the response body inside its timeout, so the whole exchange is still bounded. A cancellation by the caller stays anOperationCanceledException.AddRocketWelderClienttwice (rw2's AiTools does this after a host registration) shares one handler, so failures are never wrapped twice.Tests
RocketWelder.SDK.Http.Tests: 152 passed / 0 failed / 0 skipped; master had 107/0/0. F001-U-001 … F001-U-014 are all implemented, with the scenario id in each test name.Preview
RocketWelder.SDK.Http 3.1.2-preview.4b8abca(published bypreview-publish.ymlon the branch push; nuget.org serves it).🤖 Generated with Claude Code