Add Python client for UiPath.Ipc#125
Open
eduard-dumitru wants to merge 36 commits into
Open
Conversation
Implements a Python port of the CoreIpc framework, wire-compatible with the .NET server/client. Includes RPC server and client with TCP and Named Pipe transports, asyncio-based, zero mandatory deps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add playground/interop_with_dotnet.py that starts the .NET IpcSample.ConsoleServer and calls IComputingService + ISystemService from the Python client over named pipes. Fix named pipe client to use ProactorEventLoop's native pipe I/O instead of blocking win32file calls in an executor, which deadlocked on concurrent read/write with a non-overlapped handle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Preserves the prior client+server sketch as a reference while we rebuild the Python client from scratch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Empty package skeleton (pyproject.toml + README + __init__) ready for the phased port. Built with hatchling, requires Python >= 3.10. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add pytest as a dev extra in pyproject.toml + [tool.pytest.ini_options] - Configure VS pyproj to use pytest as the test framework - Add tests/ folder with a smoke test that verifies the package imports - Update CoreIpc.sln to drop the old Py projects and add uipath-ipc Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the explicit <Compile> and <Folder> lists with a single **\*.py glob (excluding .venv, __pycache__, build, dist, egg-info). Manage files via the filesystem to keep the glob intact; PTVS will rewrite the entry on UI-driven Add/Remove. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces uipath_ipc.wire with the four wire message types (Request, Response, CancellationRequest, Error) and the MessageType enum. All DTOs are frozen+slotted dataclasses with explicit to_dict/from_dict (snake_case <-> PascalCase) and to_json/from_json convenience methods. Covers the .NET wire-format gotcha that Request.Parameters is a list of *already JSON-encoded* strings, one per argument. 14 tests in tests/wire/test_messages.py verify round-tripping and match captured .NET-shape JSON payloads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5-byte header (uint8 MessageType + int32 LE PayloadLength) + payload. read_frame and write_frame operate against asyncio.StreamReader and a structural FrameWriter protocol (any object with write/drain). Also pulls in pytest-asyncio as a dev extra and enables asyncio_mode=auto so async test funcs run without per-test markers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-platform: \<server>\pipe\<name> on Windows (via ProactorEventLoop's create_pipe_connection), /tmp/CoreFxPipe_<name> Unix Domain Socket on POSIX (matches .NET's NamedPipeClient cross-platform convention). ClientTransport ABC abstracts the connect step, returning an (asyncio.StreamReader, asyncio.StreamWriter) pair that downstream layers (connection, proxy) consume. Transport instances are frozen+slotted dataclasses; each connect() opens a fresh stream. Top-level package re-exports ClientTransport + NamedPipeClientTransport. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps one (StreamReader, StreamWriter) pair with a background receive loop that decodes frames and resolves pending response futures by Request.id. send_request(req) sends a Request frame and awaits the matching Response. Supports `async with` for lifecycle management. Failure modes covered: underlying-stream close fails all in-flight futures; send on a closed connection raises ConnectionError. Exception translation (Error -> RemoteException) and cancellation forwarding (CancellationRequest on caller cancel) are deferred to Phase C. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IpcClient lazily opens an IpcConnection over the configured ClientTransport; reused across calls. get_proxy(contract) returns a proxy that satisfies the contract type. __getattr__ on the proxy intercepts method access — each call json.dumps each positional arg into Request.Parameters, sends the Request, awaits the matching Response, json.loads(Data) for non-null Data (or returns None). Server-returned Errors raise RemoteException (placeholder — Phase C.1 will refine the exception model: chain, type-name mapping). Keyword arguments are not supported (.NET wire is positional only); unknown method names raise AttributeError up front. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RemoteException now exposes message, type_name, stack_trace, and inner as first-class attributes. from_error(error) walks the nested wire Error chain producing a matching RemoteException chain, and sets __cause__ so Python tracebacks display the full chain naturally. str(exc) renders as "[Type] Message" when type is known. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On asyncio.CancelledError during send_request, IpcConnection fires off a best-effort CancellationRequest frame (matching the original Request.id) before re-raising. The send is a background task so the caller's cancellation propagates immediately and the message goes out asynchronously on the same writer. Failures during the cancellation send are swallowed (writer may already be closing). The original CancelledError reaches the caller intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IpcClient(transport, request_timeout=5.0) configures a single knob
that both:
- sets Request.TimeoutInSeconds on every outgoing call (server-side
deadline), and
- wraps each proxy call in asyncio.wait_for(...) (client-side
deadline → asyncio.TimeoutError).
When the client-side timeout fires, asyncio.wait_for cancels
send_request, which triggers the existing C.2 cancellation forwarding
— the server receives a CancellationRequest matching the timed-out id.
Per-call override is left to the caller via `async with asyncio.timeout(t)`
or `asyncio.wait_for(...)`. No timeout parameter on signatures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IpcConnection's receive loop now sets is_closed=True in a finally, regardless of which path exited (clean EOF, OSError, unexpected exception). IpcClient._ensure_connected sees the dead connection, acloses it cleanly (idempotent), and re-dials via the transport. The proxy instance is stable across reconnects — same get_proxy result keeps working after the underlying stream is replaced. In-flight calls when the drop happens still propagate the underlying error (no silent retry). Auto-reconnect only fires on the *next* call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps asyncio.open_connection(host, port) behind the same ClientTransport interface. Same shape as NamedPipeClientTransport — frozen+slotted dataclass, connect() returns the standard (StreamReader, StreamWriter) pair. Includes a loopback smoke test that spins up an asyncio TCP server, connects, and exchanges bytes — covering the actual networking path in addition to the constructor/immutability unit tests. Re-exported at the top-level package alongside the named-pipe one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README.md replaces the stub; mirrors the .NET README structure (install, quick start, contracts, cancellation/timeouts/errors, auto-reconnect, transports, what's out of scope) but Python-idiomatic throughout. - src/uipath_ipc/py.typed (PEP 561 marker) — signals to mypy/pyright that this package ships inline type information. - pyproject.toml: explicit [tool.hatch.build.targets.wheel].packages so hatchling reliably picks up the src layout and includes py.typed. - Smoke tests now verify the documented public surface stays exported and that py.typed travels into the package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tests/integration/ holds tests that exercise the Python client against the real IpcSample.ConsoleServer. A session-scoped fixture launches `dotnet run --framework net6.0`, waits for "Server started" on stdout, yields, then signals CTRL_BREAK (Win) / SIGINT (POSIX) to shut it down. Gated behind `--integration`. Default `pytest` skips them so the unit loop stays fast (62 passed, 7 skipped, ~0.36s). Run with `pytest --integration` to exercise the live interop path. Coverage: AddFloats, MultiplyInts, EchoString, AddComplexNumbers, DivideByZero (verifying RemoteException with type_name), and multi-call reuse on a single client. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The --integration flag becomes --no-integration. Integration tests now run as part of the default `pytest` invocation; pass --no-integration to skip them. VS Test Explorer's Run All will now include the .NET interop suite (launching IpcSample.ConsoleServer via dotnet run). First cold run incurs the dotnet build cost. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.NET's NamedPipeServerStream pattern creates pipe instances on demand — there's a small window between accepting one connection and creating the next during which CreateFile returns ERROR_FILE_NOT_FOUND. This shows up as `FileNotFoundError: [WinError 2]` for clients connecting in the wrong moment (test sessions, server restarts, deploys). NamedPipeClientTransport._connect_windows now retries on FileNotFoundError with a bounded backoff (total ~1.85s across 6 tries) before giving up. All other errors still propagate immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds IpcSample.PythonClientTestServer/ — a net8.0 console host purpose-
built for the Python integration suite:
- AddConsole() logging so handler activity is visible.
- Simple callback-free service implementations (the existing
IpcSample.ConsoleServer's MultiplyInts depends on a client-side
callback, unusable from a callback-less Python client).
- Stable "READY pipe=<name>" startup marker.
- Pipe name configurable via CLI arg (defaults to "uipath-ipc-py-test").
The Python integration fixture launches this project, runs a background
thread to drain the server's stdout, and dumps the full transcript at
session teardown for diagnostics.
Fixes a Python-side wire bug: .NET Request.TimeoutInSeconds is a
non-nullable `double`, with 0 as the "no timeout, use default" sentinel.
Emitting JSON null on this field made Newtonsoft.Json reject the entire
Request during constructor binding and the server silently dropped the
connection. Request.to_dict now emits 0.0 in place of None;
from_dict symmetrically decodes 0/0.0 back to None.
Adds tests/wire/test_dotnet_compatibility.py — 14 tests asserting the
serialized wire shape literally matches the .NET schema
(UiPath.CoreIpc/Wire/Dtos.cs). Catches this class of regression at
unit-test time without needing the integration suite to run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VS's Python Tools (and other IDE debuggers) need debugpy on the active interpreter to launch a debug session. Without it, "Debug Test" in VS Test Explorer fails with the vague "Path to debug adapter executable not specified" dialog. Putting it in [project.optional-dependencies].dev means a fresh `pip install -e ".[dev]"` after clone Just Works for debugging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a81c8a6 to
b48e3cc
Compare
Two CI-side changes needed before the Python work can land green: - Switch Npm@1's customFeed from the org-level `npm-packages` feed (managed outside CoreIpc) to a project-scoped `uipath-ipc-deps` (CoreIpc/9a5bdfb1-...). Same npmjs.org upstream, project ownership, PyPI upstream already enabled for the eventual Python publishing. - Disable the org-wide Safe Chain Guard pipeline decorator via SCG_KILL_SWITCH=true at pipeline scope. The Aikido shim that SCG injects ahead of every npm/python invocation started failing installs with Azure-Storage-SAS-shaped 403s (last green CoreIpc build was 2026-04-30, after the SCG rollout). Pipeline scope is required — task-env scope is too late, the decorator runs in pre-job. CoreIpc temporarily opts out of SCG-side malware scanning; revisit when the DevOps fix lands. See azp-nodejs.yaml's inline comment for the full Slack-referenced story so the next person on this trail doesn't repeat the dead-ends. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
abe0ccf to
b3cdd0e
Compare
Reshapes the pipeline so:
- Publishing is opt-in via parameters (`publishNuGet`, `publishNpm`,
default false). Default runs build + test, never push. When a
publish parameter is true, its stage runs and gates on its
environment's approval check.
- NuGet now follows the same approval-gated pattern as NPM, via a
new `NuGet-Packages` environment that mirrors `NPM-Packages`'
approval check. The `dotnet nuget push` moves out of
azp-dotnet-dist.yaml (which built+pushed unconditionally) into a
new `azp-nuget.publish.steps.yaml` under the gated stage.
- Publish stages can replay against a previous successful build via
`reuseArtifactsFromBuildId`. When set, the Build stage is Skipped
(not Failed) and the Publish stages download from the specified
build. When unset, both behave as before.
- Job names move from environment-centric (".NET on Windows",
"node.js on Windows", "node.js on Ubuntu") to deliverable-centric
("NuGet — .NET on Windows", "NPM — Node + Web on Windows",
"NPM — Node + Web on Linux (test-only)"). The "test-only" marker
signals the Linux job is a cross-platform check, not a second
source of artifacts.
- Rejecting an approval no longer leaves the run as Failed — the
Publish stages start in `Skipped` state when their parameter is
false, so the rejection-as-failure footgun is gone.
Out of this pass: Python jobs in Build, Python publish stage, and any
move of `PublishSymbols` into the gated NuGet publish — left as
follow-ups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reshape the NPM publish so the project-scoped Azure Artifacts feed (uipath-ipc-deps) is the primary, always-working target — the pipeline's build-service identity is already an administrator on it, no PAT rotation involved. The existing GitHub Packages publish stays wired up but is marked continueOnError: true. It's currently expected to fail: post Mini Shai-Hulud (2026-05-11/12 npm supply-chain incident), UiPath revoked classic PATs org-wide and migrated everyone to fine-grained PATs, which don't expose the Packages permission at org level. Per Liviu Bud's 2026-05-25 #dev announcement, a sanctioned pipeline-auth replacement is in progress but not yet available. When the platform team ships the replacement, updating the PublishNPM service connection and dropping continueOnError will restore the GitHub Packages publish without any other code change. Until then, runs of Publish_NPM finish as "Succeeded with issues" rather than Failed — packages still ship to uipath-ipc-deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous attempt used publishFeedCombined which the Npm@1 task ignored, making it fall back to a default registry URL (uipath.pkgs.visualstudio .com/_packaging/npm/registry/ — no project, no feed id) and 404 on PUT. The correct input name on the task is publishFeed. The value format "project/feedId" stays the same as we used for customFeed on the install side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build stage gains two parallel jobs: - Python_Windows (windows-2022): installs Python 3.12, pip installs the package with [dev] extras, runs pytest (unit + integration against the dedicated .NET test server), then python -m build to produce wheel + sdist, published as the "Python package" artifact. - Python_Linux (ubuntu-22.04): same install + pytest run; test-only, no artifact. Mirrors the NPM cross-platform pattern. New Publish_PyPI stage, gated on a new `publishPyPI` parameter (default false), uploads wheel + sdist to the project-scoped Azure Artifacts feed `uipath-ipc-deps` via twine. Approval-gated by the new `PyPI-Packages` environment, mirroring the NuGet/NPM pattern (same approver, same 12h timeout). Templates added: - azp-python.yaml (install + tests) - azp-python-dist.yaml (build wheel + sdist, publish artifact) - azp-python.publish.steps.yaml (twine upload + reuseArtifactsFromBuildId) reuseArtifactsFromBuildId works for Python the same way as for the NuGet/NPM stages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Linux CI run of tests/integration/test_add_floats failed with asyncio.open_unix_connection raising FileNotFoundError on /tmp/CoreFxPipe_uipath-ipc-py-test — the .NET test server prints its "READY" marker before the accept loop has actually bound the Unix Domain Socket file. Only the first integration test of the session hits the window; by the time the second runs, the UDS is up. The Windows _connect_windows already has a bounded retry loop on FileNotFoundError for the same class of race. Refactor: shared _CONNECT_RETRY_DELAYS now covers both code paths; _connect_posix gets the same retry shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Azure Pipelines's UI marks string parameters with empty defaults as visually required, even when our condition logic accepts empty. Use '0' as the default sentinel value meaning "no reuse, run Build normally" — natural to type, clearly not a real build id. Conditions in all four affected files now accept both '0' and '' as no-reuse for backward compatibility with any in-flight runs parameterized the old way. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Until now uipath-ipc shipped with the literal "0.1.0" from
pyproject.toml. Bring it in line with the other artifacts: the version
comes from $(FullVersion), which azp-initialization.yaml computes from
UiPath.CoreIpc.csproj plus $(Build.BuildNumber).
PEP 440 doesn't allow .NET-style pre-release suffixes ("2.5.1-20260528-08"),
so the mapping is:
- Release version "2.5.1" -> "2.5.1" (unchanged)
- Pre-release "2.5.1-20260528-08" -> "2.5.1+20260528.08" (local version)
Implementation: a tiny src/CI/stamp-python-version.py rewrites the
version line in pyproject.toml right before `python -m build` runs.
Tests already happen against the editable install (which keeps the
pre-stamp version) — they don't care about the value, just that the
package is importable.
Also wires azp-initialization.yaml into both Python jobs (previously
only used by .NET/NPM jobs), so $(FullVersion) is defined when the
stamper runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each technology's Build now runs as its own stage with `dependsOn: []`, so all three race in parallel. Each Publish_X depends only on its matching Build_X, so a slow technology (e.g. Python integration tests against the .NET test server) doesn't gate a fast one (NuGet) from publishing as soon as it's ready. Matrix jobs (NPM Windows + Linux, Python Windows + Linux) stay together inside their stage as sibling jobs — the artifact-producing Windows one and the test-only Linux one finish around the same time, so there's no benefit to splitting them further. No change to the publish-gating, environment approvals, or the reuseArtifactsFromBuildId behaviour. The only externally visible difference is wall-clock time and the stage graph in the run UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new boolean parameters, all default true. Unchecking one compile-time excludes its Build_X stage from the run — useful when you only care about one technology and don't want to wait for the others (or for selective re-runs). The matching Publish_X stage handles the missing Build_X by switching its dependsOn to [] at compile time. Combined with reuseArtifactsFromBuildId, this lets you publish-without-building from a specific prior build's artifacts, even if some technologies aren't in the current run at all. Parameter ordering on the queue-time form: Build_* first, then Publish_*, then reuseArtifactsFromBuildId — matches the natural top-to-bottom reading of "what should this run do?". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .NET server can already call back into clients via
`m.Client.GetCallback<TContract>()`. This adds the matching path on the
Python side: an `IpcClient` constructed with `callbacks={Contract: impl}`
hosts the contract; inbound REQUEST frames are dispatched to the
registered instance and a Response frame is written back.
Wire/dispatch details:
- IpcConnection now owns a write lock (concurrent outbound responses
from multiple callback handlers stay frame-aligned) and a handler-task
registry keyed by request id.
- Incoming REQUEST: deserialize, look up endpoint by interface name
(Contract.__name__), look up method by name, json.loads each parameter
individually (matches the .NET wire convention), invoke (awaitable or
sync), encode the result into Response.data.
- Handler exceptions: build Response.Error with type_name / message /
stack_trace; the .NET side raises RemoteException as normal.
- Incoming CANCELLATION_REQUEST: cancels the matching handler task; the
emitted error mimics .NET's OperationCanceledException so the server
sees what it would from any other client.
- Callback methods must NOT declare CancellationToken parameters — the
server-side caller doesn't include CT in the wire Parameters array
(matches the existing .NET IComputingCallback convention).
Tests:
- tests/client/test_callbacks.py — 7 unit tests covering happy path,
multi-arg, concurrent inbound requests, handler exception → error
response, unknown endpoint, unknown method, server cancellation.
- tests/integration/test_dotnet_interop.py — 3 round-trip tests against
the new ICallbackTester on the .NET sample server.
- IpcSample.PythonClientTestServer gains IClientCallback /
ICallbackTester / CallbackTester so the Python integration tests have
a real server to bounce callbacks off of.
Version bumped to 0.2.0 (callbacks are an additive feature; the
client-only-no-callback API from 0.1.0 is unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The proxy did `if resp.data is None: return None` then `json.loads(resp.data)`.
But void / fire-and-forget operations answer with an empty Data *string*
(not null) — e.g. .NET CoreIpc's response for a Task-returning method — so
json.loads("") raised JSONDecodeError. This surfaced in a consumer as a
crash on IUserOperations.Subscribe() (a void op): the call succeeds on the
wire, but parsing its empty Data blew up.
Fix: `if not resp.data: return None` (covers null and empty string). Adds
test_proxy_empty_data_return alongside the existing void (null) test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Python client for UiPath.Ipc that speaks the same wire protocol as the .NET package, plus the CI/CD plumbing to build, test, and publish it.
uipath-ipc 0.1.0is published and resolvable: https://uipath.visualstudio.com/CoreIpc/_artifacts/feed/uipath-ipc-deps/PyPI/uipath-ipc/overview/0.1.0Update: bidirectional callbacks added (uipath-ipc 0.2.0, pending publish on this run).
Python client (
src/Clients/python/uipath-ipc/)/tmp/CoreFxPipe_*) and TCP, both with bounded retry onFileNotFoundErrorto ride out the server's accept-loop warm-up.IpcClient(transport=..., callbacks={IClientCallback: impl}): lazy connect, dynamic__getattr__proxy viaget_proxy(IContract), auto-reconnect on transport drop, optionalrequest_timeout.RemoteExceptionpreservesmessage/type_name/stack_trace/innerchain, sets__cause__for traceback display.CancellationTokenparameter on signatures. The proxy forwardsCancellationRequeston the wire when its task is cancelled; server-initiated cancellation aborts the matching in-flight callback handler.py3-none-anywith PEP 561py.typed.Callback (server → client) support — 0.2.0
IpcClient(callbacks={Contract: instance})registers handler objects keyed by interface name.IpcConnectionnow dispatches incoming REQUEST frames: looks up endpoint by interface name, resolves method by name, json.loads each parameter individually (matching the .NET convention), invokes (awaitable or sync), and writes a Response back. A write lock keeps concurrent outbound frames aligned.RemoteException. Server-initiatedCancellationRequestcancels the matching handler task; the emitted error mimics .NET'sOperationCanceledException.CancellationTokenparameters — the server-side caller doesn't include CT in the wireParametersarray (matches the existing .NETIComputingCallbackconvention).ICallbackTesterendpoint on the sample server.Dedicated .NET test server (
src/IpcSample.PythonClientTestServer/)Purpose-built for the Python integration suite — net8.0, console logging on, stable
READY pipe=<name>startup marker. HostsIComputingService/ISystemService(callback-free) plus the newICallbackTesterused to exercise the server→client callback path.Wire-shape tests
tests/wire/test_dotnet_compatibility.pyasserts our serialized JSON against the .NET schema literally (field sets, types,TimeoutInSecondsis a non-null double, etc.) — catches a class of regression at unit-test time that previously only surfaced when the live integration suite ran. This came out of finding (and fixing) an actual mismatch:TimeoutInSeconds: nullmade Newtonsoft drop the entire Request silently.CI / pipeline redesign
publishNuGet,publishNpm,publishPyPI(all defaultfalse). Default CI runs build + test only. Old behavior of always-pushing NuGet on every branch and always-popping an approval prompt is gone — and a rejected approval no longer leaves the run as Failed.buildNuGet,buildNpm,buildPython(all defaulttrue).reuseArtifactsFromBuildIdparameter lets a Publish stage replay against a prior successful build, skipping Build entirely.(test-only)marker disambiguates the Linux matrix runs from the artifact-producing Windows ones.NuGet-PackagesandPyPI-Packagesmirror the existingNPM-Packagesapproval shape (same approver, same 12h timeout).Feed swap + Supply Chain Guard bypass
npm installcustomFeedmoved from the org-levelnpm-packages(managed outside CoreIpc) to a project-scopeduipath-ipc-deps(CoreIpc-owned, mirrors npmjs.org + PyPI + NuGet).SCG_KILL_SWITCH=trueto bypass the org-wide Aikido Safe Chain shim, which was interfering with npm tarball downloads. Comments inazp-start.yamlandazp-nodejs.yamldocument the trail (with #devops Slack links) so the next person on this doesn't redo the dead-ends.NPM publish
uipath-ipc-depsfirst (uses the pipeline's built-in identity — no PAT). GitHub Packages stays wired up ascontinueOnError: trueuntil the platform team ships the post-Mini-Shai-Hulud pipeline-auth replacement (tracked in Liviu Bud's #dev thread).Publish_NPMfinish as "Succeeded with issues" until the GitHub side resolves — packages still ship touipath-ipc-deps.Test plan
Publish_NuGetexercised against a prior successful build viareuseArtifactsFromBuildId.Publish_NPMpublishes touipath-ipc-deps(GitHub Packages step warns).Publish_PyPIpublishesuipath-ipc-0.1.0touipath-ipc-deps.Publish_PyPIpublishesuipath-ipc-0.2.0(callbacks) touipath-ipc-deps.pip install --index-url <feed> uipath-ipcin a fresh venv on a separate project — proves the consumer flow round-trips..NETflakeSystemTestsOverTcp.NotPassingAnOptionalMessage_ShouldWork(tight 800ms timeout, pre-dates this PR). Tracked separately.continueOnErroronce platform team ships sanctioned pipeline auth.src/Clients/python/_attempt0/(preserved as reference during the port).🤖 Generated with Claude Code