From 2c66f55aa907a96e96b01edd84ebc8e417af0731 Mon Sep 17 00:00:00 2001 From: eanzhao Date: Tue, 25 Aug 2026 16:18:41 +0800 Subject: [PATCH 1/2] Admit code execution routes from keys auto_connected NyxID /user-services omits auto_connected; only /keys returns it. Missing fields were treated as writable, so platform-hosted routes received PUT 403, personal POST omitted the required label, and non-2xx bodies were swallowed as PostconditionMismatch. Read auto_connected only from /keys, mutate only when it is explicitly false, send a readable personal label, and preserve HTTP rejection as MutationRejected. --- .../NyxIdApiAccessContracts.cs | 6 +- .../NyxIdApiClient.cs | 35 ++++- ...NyxIdCodeExecutionRoutePolicyReconciler.cs | 60 +++++--- .../NyxIdUserServiceRouteConverger.cs | 37 ++++- ...odeExecutionRouteAdmissionPreparerTests.cs | 139 +++++++++++++++++- 5 files changed, 236 insertions(+), 41 deletions(-) diff --git a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiAccessContracts.cs b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiAccessContracts.cs index 265322a1ce..b409b8fb46 100644 --- a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiAccessContracts.cs +++ b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiAccessContracts.cs @@ -117,7 +117,8 @@ public sealed record NyxIdUserServiceKey( NyxIdUserServiceCredentialSource CredentialSource, string? CatalogServiceId, string? CatalogServiceSlug, - bool Connected); + bool Connected, + bool? AutoConnected = null); public sealed record NyxIdUserServiceKeys(IReadOnlyList Services); @@ -425,7 +426,8 @@ private static NyxIdUserServiceKeys ParseUserServiceKeysDocument(JsonElement roo JsonValueKind.Object)), ReadOptionalNormalizedString(serviceElement, "catalog_service_id"), ReadOptionalNormalizedString(serviceElement, "catalog_service_slug"), - RequireBoolean(serviceElement, "connected"))); + RequireBoolean(serviceElement, "connected"), + ReadOptionalBoolean(serviceElement, "auto_connected"))); } return new NyxIdUserServiceKeys(services); diff --git a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiClient.cs b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiClient.cs index 96e3e28069..c654529341 100644 --- a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiClient.cs +++ b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiClient.cs @@ -285,6 +285,12 @@ public Task DeleteServiceAsync(string token, string id, CancellationToke public Task CreateServiceAsync(string token, string body, CancellationToken ct) => PostAsync(token, "/api/v1/keys", body, ct); + internal Task CreateServiceResponseAsync( + string token, + string body, + CancellationToken ct) => + PostTextResponseAsync(token, "/api/v1/keys", body, ct); + // ─── Session Refresh ─── public async Task RefreshSessionAsync(string refreshToken, CancellationToken ct) @@ -1309,6 +1315,17 @@ public Task UpdateServiceAsync(string token, string id, string body, Can public Task UpdateServiceRouteAsync(string token, string id, string body, CancellationToken ct) => PutAsync(token, $"/api/v1/user-services/{Uri.EscapeDataString(id)}", body, ct); + internal Task UpdateServiceRouteResponseAsync( + string token, + string id, + string body, + CancellationToken ct) => + PutTextResponseAsync( + token, + $"/api/v1/user-services/{Uri.EscapeDataString(id)}", + body, + ct); + // ─── Proxy (additions) ─── public Task DiscoverProxyServicesAsync(string token, CancellationToken ct) => @@ -1886,12 +1903,19 @@ private async Task GetBoundedAsync( } internal async Task PostAsync(string token, string path, string body, CancellationToken ct) + => (await PostTextResponseAsync(token, path, body, ct)).Content; + + private async Task PostTextResponseAsync( + string token, + string path, + string body, + CancellationToken ct) { var url = $"{GetPublicApiBaseUrl()}{path}"; using var request = new HttpRequestMessage(HttpMethod.Post, url); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = new StringContent(body, Encoding.UTF8, "application/json"); - return await SendAsync(request, ct); + return await SendTextResponseAsync(request, ct); } internal async Task PostWithoutAuthAsync(string path, string body, CancellationToken ct) @@ -1912,12 +1936,19 @@ internal async Task PatchAsync(string token, string path, string body, C } internal async Task PutAsync(string token, string path, string body, CancellationToken ct) + => (await PutTextResponseAsync(token, path, body, ct)).Content; + + private async Task PutTextResponseAsync( + string token, + string path, + string body, + CancellationToken ct) { var url = $"{GetPublicApiBaseUrl()}{path}"; using var request = new HttpRequestMessage(HttpMethod.Put, url); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = new StringContent(body, Encoding.UTF8, "application/json"); - return await SendAsync(request, ct); + return await SendTextResponseAsync(request, ct); } internal async Task DeleteAsync(string token, string path, CancellationToken ct) diff --git a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdCodeExecutionRoutePolicyReconciler.cs b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdCodeExecutionRoutePolicyReconciler.cs index 0ec9f1491e..6547ce4415 100644 --- a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdCodeExecutionRoutePolicyReconciler.cs +++ b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdCodeExecutionRoutePolicyReconciler.cs @@ -1,4 +1,6 @@ +using System.Net; using System.Text.Json; +using System.Text.Json.Serialization; using Aevatar.AI.Abstractions.CodeExecution; namespace Aevatar.AI.ToolProviders.NyxId; @@ -8,6 +10,7 @@ public enum NyxIdCodeExecutionRouteRepairFailureKind None = 0, UpdateException = 1, PostconditionMismatch = 2, + MutationRejected = 3, } public sealed record NyxIdCodeExecutionRouteReconciliation( @@ -74,10 +77,14 @@ public async Task ReconcileAsync( Verified: postconditionSatisfied, FailureKind: postconditionSatisfied ? NyxIdCodeExecutionRouteRepairFailureKind.None - : convergence.FailureKind == - NyxIdUserServiceRouteConvergenceFailureKind.UpdateException - ? NyxIdCodeExecutionRouteRepairFailureKind.UpdateException - : NyxIdCodeExecutionRouteRepairFailureKind.PostconditionMismatch); + : convergence.FailureKind switch + { + NyxIdUserServiceRouteConvergenceFailureKind.UpdateException => + NyxIdCodeExecutionRouteRepairFailureKind.UpdateException, + NyxIdUserServiceRouteConvergenceFailureKind.MutationRejected => + NyxIdCodeExecutionRouteRepairFailureKind.MutationRejected, + _ => NyxIdCodeExecutionRouteRepairFailureKind.PostconditionMismatch, + }); } if (!CanCreatePersonalRoute(before, resolution, exactUserServiceId)) @@ -137,14 +144,13 @@ private static bool CanCreatePersonalRoute( .Where(service => CodeExecutionContract.IsSupportedServiceSlug(service.Slug) && snapshot.TryGetExact(service.Id, out var authority) && - authority is { IsExecutionReady: true } && + authority is { IsExecutionReady: true, Execution.AutoConnected: true } && string.Equals( authority.Execution.CatalogServiceSlug, CodeExecutionContract.ServiceSlug, StringComparison.Ordinal)) .ToArray(); return canonical.Length == 1 && - canonical[0].AutoConnected && string.Equals( canonical[0].Slug, CodeExecutionContract.ServiceSlug, @@ -156,23 +162,24 @@ private async Task CreateAndVerifyPersona NyxIdUserServiceRouteMutationAuthority mutationAuthority, CancellationToken cancellationToken) { - var createFailed = false; + var createFailure = NyxIdCodeExecutionRouteRepairFailureKind.None; try { - var body = JsonSerializer.Serialize(new - { - service_slug = CodeExecutionContract.ServiceSlug, - slug = CodeExecutionContract.PersonalServiceSlug, - forward_access_token = true, - inject_delegation_token = true, - delegation_token_scope = "proxy:* sandbox:execute", - }); - _ = await _clientFactory.CreateClient() - .CreateServiceAsync( + var body = JsonSerializer.Serialize(new CreatePersonalRouteRequest( + CodeExecutionContract.ServiceSlug, + CodeExecutionContract.PersonalServiceSlug, + "Aevatar Code Execution", + true, + true, + "proxy:* sandbox:execute")); + var response = await _clientFactory.CreateClient() + .CreateServiceResponseAsync( mutationAuthority.BearerToken, body, cancellationToken) .ConfigureAwait(false); + if (!response.Succeeded && response.HttpStatus != (int)HttpStatusCode.Conflict) + createFailure = NyxIdCodeExecutionRouteRepairFailureKind.MutationRejected; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { @@ -180,7 +187,7 @@ private async Task CreateAndVerifyPersona } catch { - createFailed = true; + createFailure = NyxIdCodeExecutionRouteRepairFailureKind.UpdateException; } var after = await _converger.ReadAsync(mutationAuthority, cancellationToken) @@ -196,9 +203,9 @@ private async Task CreateAndVerifyPersona Verified: verified, FailureKind: verified ? NyxIdCodeExecutionRouteRepairFailureKind.None - : createFailed - ? NyxIdCodeExecutionRouteRepairFailureKind.UpdateException - : NyxIdCodeExecutionRouteRepairFailureKind.PostconditionMismatch); + : createFailure == NyxIdCodeExecutionRouteRepairFailureKind.None + ? NyxIdCodeExecutionRouteRepairFailureKind.PostconditionMismatch + : createFailure); } private static NyxIdUserService? SelectVerifiedPersonalRoute( @@ -213,10 +220,9 @@ private async Task CreateAndVerifyPersona service.Slug, CodeExecutionContract.PersonalServiceSlug, StringComparison.Ordinal) && - !service.AutoConnected && service.CredentialSource.Kind == NyxIdUserServiceCredentialSourceKind.Personal && snapshot.TryGetExact(service.Id, out var authority) && - authority is { IsExecutionReady: true } && + authority is { IsExecutionReady: true, Execution.AutoConnected: false } && string.Equals( authority.Execution.CatalogServiceSlug, CodeExecutionContract.ServiceSlug, @@ -225,4 +231,12 @@ private async Task CreateAndVerifyPersona .ToArray(); return candidates.Length == 1 ? candidates[0] : null; } + + private sealed record CreatePersonalRouteRequest( + [property: JsonPropertyName("service_slug")] string ServiceSlug, + [property: JsonPropertyName("slug")] string Slug, + [property: JsonPropertyName("label")] string Label, + [property: JsonPropertyName("forward_access_token")] bool ForwardAccessToken, + [property: JsonPropertyName("inject_delegation_token")] bool InjectDelegationToken, + [property: JsonPropertyName("delegation_token_scope")] string DelegationTokenScope); } diff --git a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdUserServiceRouteConverger.cs b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdUserServiceRouteConverger.cs index 42e866597a..79bd122e83 100644 --- a/src/Aevatar.AI.ToolProviders.NyxId/NyxIdUserServiceRouteConverger.cs +++ b/src/Aevatar.AI.ToolProviders.NyxId/NyxIdUserServiceRouteConverger.cs @@ -216,7 +216,9 @@ public sealed record NyxIdUserServiceAuthority( : Execution.NodeStatus == NyxIdUserServiceNodeStatus.Online); public bool CanManageRoute => - !Route.AutoConnected && IsExecutionReady && Route.CredentialSource.Kind switch + Execution.AutoConnected == false && + IsExecutionReady && + Route.CredentialSource.Kind switch { NyxIdUserServiceCredentialSourceKind.Personal => true, NyxIdUserServiceCredentialSourceKind.Organization => @@ -240,7 +242,7 @@ internal bool HasSameIdentity(NyxIdUserServiceAuthority other) => Execution.CatalogServiceSlug, other.Execution.CatalogServiceSlug, StringComparison.Ordinal) && - Route.AutoConnected == other.Route.AutoConnected && + Execution.AutoConnected == other.Execution.AutoConnected && SameAuthority(Execution.CredentialSource, other.Execution.CredentialSource); private static bool SameAuthority( @@ -291,9 +293,29 @@ public async Task ReadAsync( bearerToken.Trim(), cancellationToken) .ConfigureAwait(false); + var routes = NyxIdApiAccessResponseParser.ParseUserServiceRoutes(routeResponse); + var executionInventory = NyxIdApiAccessResponseParser.ParseUserServiceKeys(executionResponse); return new NyxIdUserServiceAuthoritySnapshot( - NyxIdApiAccessResponseParser.ParseUserServiceRoutes(routeResponse), - NyxIdApiAccessResponseParser.ParseUserServiceKeys(executionResponse)); + ApplyExecutionProvenance(routes, executionInventory), + executionInventory); + } + + private static NyxIdApiAccessResult ApplyExecutionProvenance( + NyxIdApiAccessResult routes, + NyxIdApiAccessResult executionInventory) + { + if (!routes.Succeeded || !executionInventory.Succeeded) + return routes; + + var executionById = executionInventory.Value!.Services + .ToDictionary(static service => service.Id, StringComparer.Ordinal); + var normalizedRoutes = routes.Value!.Services + .Select(route => executionById.TryGetValue(route.Id, out var execution) + ? route with { AutoConnected = execution.AutoConnected == true } + : route) + .ToArray(); + return NyxIdApiAccessResult.Success( + new NyxIdUserServices(normalizedRoutes)); } } @@ -306,6 +328,7 @@ public enum NyxIdUserServiceRouteConvergenceFailureKind RouteNotWritable = 4, UpdateException = 5, PostconditionMismatch = 6, + MutationRejected = 7, } public sealed record NyxIdUserServiceRouteConvergence( @@ -380,13 +403,15 @@ internal async Task ConvergeAsync( try { var body = NyxIdUserServiceRouteUpdateAdapter.Serialize(plan.Patch); - _ = await clientFactory.CreateClient() - .UpdateServiceRouteAsync( + var response = await clientFactory.CreateClient() + .UpdateServiceRouteResponseAsync( authority.BearerToken, current.Route.Id, body, cancellationToken) .ConfigureAwait(false); + if (!response.Succeeded) + updateFailure = NyxIdUserServiceRouteConvergenceFailureKind.MutationRejected; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { diff --git a/test/Aevatar.AI.Tests/NyxIdCodeExecutionRouteAdmissionPreparerTests.cs b/test/Aevatar.AI.Tests/NyxIdCodeExecutionRouteAdmissionPreparerTests.cs index dcde7edef5..44978a4304 100644 --- a/test/Aevatar.AI.Tests/NyxIdCodeExecutionRouteAdmissionPreparerTests.cs +++ b/test/Aevatar.AI.Tests/NyxIdCodeExecutionRouteAdmissionPreparerTests.cs @@ -22,7 +22,9 @@ public async Task AdmitAsync_AutoConnectedPlatformRoute_CreatesPersonalRouteAndC AutoConnectedKeysInventory(), AutoConnectedInventory(), AutoConnectedKeysInventory(), - """{"error":true,"status":409,"body":"concurrent create"}""", + new SequenceResponse( + HttpStatusCode.Conflict, + """{"error":"concurrent create"}"""), PersonalExecutionInventory(), PersonalExecutionKeysInventory(), PersonalExecutionInventory(), @@ -79,6 +81,8 @@ public async Task AdmitAsync_AutoConnectedPlatformRoute_CreatesPersonalRouteAndC { body.RootElement.GetProperty("service_slug").GetString().Should().Be("chrono-sandbox"); body.RootElement.GetProperty("slug").GetString().Should().Be("chrono-sandbox-aevatar"); + body.RootElement.GetProperty("label").GetString().Should() + .Be("Aevatar Code Execution"); body.RootElement.GetProperty("forward_access_token").GetBoolean().Should().BeTrue(); body.RootElement.GetProperty("inject_delegation_token").GetBoolean().Should().BeTrue(); body.RootElement.GetProperty("delegation_token_scope").GetString().Should() @@ -90,6 +94,91 @@ public async Task AdmitAsync_AutoConnectedPlatformRoute_CreatesPersonalRouteAndC proof.CatalogServiceId.Should().Be("catalog-chrono-sandbox"); } + [Fact] + public async Task ReconcileAsync_PersonalRouteCreationRejected_PreservesMutationFailure() + { + var handler = new SequenceHandler( + AutoConnectedInventory(), + AutoConnectedKeysInventory(), + new SequenceResponse( + HttpStatusCode.UnprocessableEntity, + """{"error":"missing field `label`"}"""), + AutoConnectedInventory(), + AutoConnectedKeysInventory()); + var options = new NyxIdToolOptions { BaseUrl = "https://nyx.example" }; + var reconciler = new NyxIdCodeExecutionRoutePolicyReconciler( + new TestClientFactory(new NyxIdApiClient(options, new HttpClient(handler)))); + NyxIdUserServiceRouteMutationAuthority.TryCreate( + NyxIdCallerCredentialSelection.DirectUserBearer("source-readable-alpha"), + out var mutationAuthority) + .Should().BeTrue(); + + var result = await reconciler.ReconcileAsync(mutationAuthority!); + + result.Attempted.Should().BeTrue(); + result.Verified.Should().BeFalse(); + result.FailureKind.Should().Be(NyxIdCodeExecutionRouteRepairFailureKind.MutationRejected); + handler.Requests.Select(static request => request.Method).Should().Equal( + HttpMethod.Get, + HttpMethod.Get, + HttpMethod.Post, + HttpMethod.Get, + HttpMethod.Get); + } + + [Fact] + public async Task ConvergeAsync_RouteMutationRejected_PreservesMutationFailure() + { + var handler = new SequenceHandler( + Inventory("personal", false, true, "proxy:*"), + KeysInventory("personal"), + new SequenceResponse(HttpStatusCode.Forbidden, """{"error":"forbidden"}"""), + Inventory("personal", false, true, "proxy:*"), + KeysInventory("personal")); + var options = new NyxIdToolOptions { BaseUrl = "https://nyx.example" }; + var converger = new NyxIdUserServiceRouteConverger( + new TestClientFactory(new NyxIdApiClient(options, new HttpClient(handler)))); + NyxIdUserServiceRouteMutationAuthority.TryCreate( + NyxIdCallerCredentialSelection.DirectUserBearer("source-readable-alpha"), + out var mutationAuthority) + .Should().BeTrue(); + + var result = await converger.ConvergeAsync( + mutationAuthority!, + "us-code-alpha", + new NyxIdUserServiceRouteContract( + NyxIdUserServiceBooleanRequirement.Enabled, + NyxIdUserServiceBooleanRequirement.Enabled, + ["proxy:*", "sandbox:execute"])); + + result.Attempted.Should().BeTrue(); + result.Verified.Should().BeFalse(); + result.FailureKind.Should().Be( + NyxIdUserServiceRouteConvergenceFailureKind.MutationRejected); + } + + [Fact] + public async Task ReconcileAsync_KeysOmitAutoConnected_DoesNotGuessWritable() + { + var handler = new SequenceHandler( + AutoConnectedInventory(), + AutoConnectedKeysInventoryOmittingAutoConnected()); + var options = new NyxIdToolOptions { BaseUrl = "https://nyx.example" }; + var reconciler = new NyxIdCodeExecutionRoutePolicyReconciler( + new TestClientFactory(new NyxIdApiClient(options, new HttpClient(handler)))); + NyxIdUserServiceRouteMutationAuthority.TryCreate( + NyxIdCallerCredentialSelection.DirectUserBearer("source-readable-alpha"), + out var mutationAuthority) + .Should().BeTrue(); + + var result = await reconciler.ReconcileAsync(mutationAuthority!); + + result.Attempted.Should().BeFalse(); + result.Verified.Should().BeFalse(); + handler.Requests.Should().HaveCount(2); + handler.Requests.Should().OnlyContain(static request => request.Method == HttpMethod.Get); + } + [Fact] public async Task AdmitAsync_LegacyPersonalRoute_RepairsThenCommitsVerifiedExactProof() { @@ -443,6 +532,7 @@ public async Task PrepareAsync_RouteWithoutReadyExecutionAuthority_DoesNotMutate "is_active": true, "status": "expired", "connected": true, + "auto_connected": false, "credential_source": { "type": "personal" } }] } @@ -653,6 +743,7 @@ private static string KeysInventory( is_active = true, status = "active", connected = true, + auto_connected = false, credential_source = credentialSource, }, }, @@ -670,7 +761,6 @@ private static string AutoConnectedInventory() => slug = "chrono-sandbox", catalog_service_id = "catalog-chrono-sandbox", is_active = true, - auto_connected = true, forward_access_token = true, inject_delegation_token = true, delegation_token_scope = "proxy:*", @@ -680,6 +770,26 @@ private static string AutoConnectedInventory() => }); private static string AutoConnectedKeysInventory() => + JsonSerializer.Serialize(new + { + keys = new[] + { + new + { + id = "us-code-platform", + slug = "chrono-sandbox", + catalog_service_id = "catalog-chrono-sandbox", + catalog_service_slug = "chrono-sandbox", + is_active = true, + status = "active", + connected = true, + auto_connected = true, + credential_source = new { type = "personal" }, + }, + }, + }); + + private static string AutoConnectedKeysInventoryOmittingAutoConnected() => JsonSerializer.Serialize(new { keys = new[] @@ -709,7 +819,6 @@ private static string PersonalExecutionInventory() => slug = "chrono-sandbox", catalog_service_id = "catalog-chrono-sandbox", is_active = true, - auto_connected = true, forward_access_token = true, inject_delegation_token = true, delegation_token_scope = "proxy:*", @@ -721,7 +830,6 @@ private static string PersonalExecutionInventory() => slug = "chrono-sandbox-aevatar", catalog_service_id = "catalog-chrono-sandbox", is_active = true, - auto_connected = false, forward_access_token = true, inject_delegation_token = true, delegation_token_scope = "proxy:* sandbox:execute", @@ -744,6 +852,7 @@ private static string PersonalExecutionKeysInventory() => is_active = true, status = "active", connected = true, + auto_connected = true, credential_source = new { type = "personal" }, }, new @@ -755,6 +864,7 @@ private static string PersonalExecutionKeysInventory() => is_active = true, status = "active", connected = true, + auto_connected = false, credential_source = new { type = "personal" }, }, }, @@ -813,6 +923,7 @@ private static string MixedKeysInventory() => is_active = true, status = "active", connected = true, + auto_connected = false, credential_source = new { type = "personal" }, }, new @@ -824,6 +935,7 @@ private static string MixedKeysInventory() => is_active = true, status = "active", connected = true, + auto_connected = false, credential_source = new { type = "org", @@ -850,6 +962,7 @@ private static string MultiplePersonalKeysInventory() => is_active = true, status = "active", connected = true, + auto_connected = false, credential_source = new { type = "personal" }, }, new @@ -861,6 +974,7 @@ private static string MultiplePersonalKeysInventory() => is_active = true, status = "active", connected = true, + auto_connected = false, credential_source = new { type = "personal" }, }, }, @@ -914,9 +1028,15 @@ public Task ParseInlineWorkflowBundleAsync( CancellationToken ct = default) => throw new InvalidOperationException("Unexpected inline bundle parse."); } - private sealed class SequenceHandler(params string[] responses) : HttpMessageHandler + private sealed class SequenceHandler(params object[] responses) : HttpMessageHandler { - private readonly Queue _responses = new(responses); + private readonly Queue _responses = new(responses.Select(static response => + response switch + { + string body => new SequenceResponse(HttpStatusCode.OK, body), + SequenceResponse typed => typed, + _ => throw new ArgumentException("Unsupported response fixture.", nameof(responses)), + })); public List Requests { get; } = []; @@ -935,16 +1055,19 @@ protected override async Task SendAsync( if (_responses.Count == 0) throw new InvalidOperationException("Unexpected NyxID request."); - return new HttpResponseMessage(HttpStatusCode.OK) + var response = _responses.Dequeue(); + return new HttpResponseMessage(response.StatusCode) { Content = new StringContent( - _responses.Dequeue(), + response.Body, Encoding.UTF8, "application/json"), }; } } + private sealed record SequenceResponse(HttpStatusCode StatusCode, string Body); + private sealed record RecordedRequest( HttpMethod Method, string Uri, From 988d3b3b4ac5f65e358e7136900d6615a62e90f6 Mon Sep 17 00:00:00 2001 From: eanzhao Date: Tue, 25 Aug 2026 16:18:47 +0800 Subject: [PATCH 2/2] Refresh NyxID conformance source pin Repoint the Aevatar conformance digest pin at the code execution route admission commit so the fast gate validates current HEAD sources. --- docs/contracts/nyxid-assistant-conformance/v1/sources.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/contracts/nyxid-assistant-conformance/v1/sources.json b/docs/contracts/nyxid-assistant-conformance/v1/sources.json index 56561304c3..23b8f5d665 100644 --- a/docs/contracts/nyxid-assistant-conformance/v1/sources.json +++ b/docs/contracts/nyxid-assistant-conformance/v1/sources.json @@ -2,8 +2,8 @@ "schema_version": 1, "aevatar": { "repository": "https://github.com/AevatarAI/aevatar.git", - "revision": "0b8ec500087331c3d12819b532e7dfa29e740fb4", - "contract_files_sha256": "0d05d7c2d12437b8281c5ee8a0210b51d9a2757c0ae9be237aab485050524420", + "revision": "2c66f55aa907a96e96b01edd84ebc8e417af0731", + "contract_files_sha256": "d0daddc0594a0f92fe7c2057c4433a7dbb06f2d8097aa0d079e67f5eab361c50", "files": { "agents/Aevatar.GAgents.NyxidChat/NyxIdActionPostconditionPort.cs": "23fd2cf48541c4b8da3fa1ef07277a7700c8478f9c638be8221465bf877fbb31", "agents/Aevatar.GAgents.NyxidChat/NyxIdAssistantActionRegistry.cs": "60e6f67c94ae11b1bf0dac036ad8ac0c35901e31787b1f0c8173964f6a12d263", @@ -13,7 +13,7 @@ "agents/Aevatar.GAgents.NyxidChat/protos/nyxid_chat_task.proto": "523b8182bdd0a30224e001a7257ad42450226f42a84542a363e97816e34f23d9", "docs/adr/0048-nyxid-assistant-operation-class-boundary.md": "884aca09774e773e68154c923fec8078610b2cf8e97f581fedc36e10451ccec3", "src/Aevatar.AI.Abstractions/ai_messages.proto": "7ca08d69adbd97d82b89fc041d8d0eebd81e3ca125e2c4798bf012f1c29dc26d", - "src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiAccessContracts.cs": "2c679f03818997d1759b5bc60560b37119214bd6d5ee64f41578e02fa44cc9ee", + "src/Aevatar.AI.ToolProviders.NyxId/NyxIdApiAccessContracts.cs": "f209169e3ccb63ec9135b7d1f50d61987890f195efbde5676bbfa38680169748", "src/Aevatar.AI.ToolProviders.NyxId/NyxIdAssistantToolSource.cs": "1b033df9cb55c741e9b52054cbd4a91067f03c8c3797bd076a7e3d6133eb0fcb", "src/Aevatar.AI.ToolProviders.NyxId/Tools/NyxIdRequestKeyCreateTool.cs": "2c4f2cda99154f2e667c6cfd291497e697ef11df17f081f96ec70070a8af8b8c", "src/Aevatar.AI.ToolProviders.NyxId/Tools/NyxIdRequestKeyRotateTool.cs": "18212bb64644cfbca401065bccce439ea5fa00316deff57d730a0d9ac2650e53",