Repository navigation
feat: add localisation keys to Astra tool result cards - #804
Merged
thomasluizon merged 2 commits intoOct 19, 2026
Merged
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the localisation metadata, action-card construction paths, serialization contracts, tests, and sibling consumer compatibility; no Critical or High findings identified.
- Additive contract: Optional
messageKeyand typedparamspreserve legacy action fields, with matching additions to both OpenAPI documents. - Result formatting: Entity actions, bulk results, preferences, failures, suggestions, and clarification cards receive translation metadata; bulk counts and partial state match the existing producers.
- Consumer sequencing: Checked
orbit-ui-mobileschemas and paired PR #1885, which explicitly leaves message-key consumption pending API ticket #23. - Verification: All 331 focused formatter, chat-handler, and emoji-tool tests passed locally, including legacy JSON compatibility and final SSE numeric parameters. The full solution suite was not rerun.
gpt-6.1-sol | 𝕏
There was a problem hiding this comment.
✅ No new Critical or High issues found.
Reviewed changes Reviewed the changes since 77ece1ae, with the complete PR diff and sibling consumer schema as context.
- Expanded failure metadata: Added specific failure keys and typed parameters for policy denials, argument validation, and entity lookup errors while preserving legacy error fields.
- Aligned sanitization: Replaced failure metadata with the generic
toolFailurekey and empty parameters when existing turn-level error sanitization runs. - Refined the contract: Replaced the unshipped generic value parameter with named fields and restricted the new DTO's numeric OpenAPI properties to integer/null without changing existing action fields.
- Strengthened verification: Added registry-derived write-action coverage and serializer/schema regressions. Sequential local runs passed all 426 focused Application tests and all 3 serializer/schema tests; the full solution suite was not rerun.
gpt-6.1-sol | 𝕏
|
This was referenced Oct 20, 2026
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.




Astra action cards now carry optional
messageKeyand typedparams, so clients can render localised card text while shipped clients continue reading the unchangedtype,entityName,error, suggestion and clarification fields.Closes thomasluizon/orbit-tickets#23 (ORB-17). The consumer is thomasluizon/orbit-tickets#24 and ships after the API.
ProcessUserChatCommand.csappends the contract fields.ToolResultMessageParams.csdeclares their types.ToolResultMessages.csformats metadata from the existing tool call and repository-owned result payloads. Both card construction partials attach it to every emitted action, including policy failures, unknown tools, repeated calls, suggestions and clarification. This keeps executor, audit and persistence contracts unchanged and avoids parsing English summaries for counts. Both generated OpenAPI documents are included. Tests cover every formatter, real emoji execution through the handler, legacy JSON and enriched response round trips.Assumptions
redesign/mainbaseline;origin/mainalso has the same unkeyed action builder. The example now uses applied, matched, skipped and partial counts rather than the shorter quoted sentence, so metadata follows the current producer rather than restoring older copy.ActionResultcollection. Read-only tool output, audit operation summaries and model free text are excluded rather than treating every model-facing tool payload as a display card.entityNameanderrorfields are preserved rather than adding or renaming a legacy text field.fieldName, rather than duplicating keys for each field.Manual steps
thomasluizon/orbit-api, runRelease API(.github/workflows/release.yml) frommain, withenvironment=stagingandbranch=redesign/mainfor staging. For production useenvironment=production,branch=mainafter the change reachesmain. Verify a chat action response containsmessageKeyand objectparams, alongside the existing legacy fields. This worker does not merge or deploy.Deploy the API before shipping client ticket #24. In GitHub Actions > Release API, dispatch the workflow from
mainwithenvironment=stagingandbranch=redesign/mainafter integration. Production deployment usesenvironment=productionandbranch=mainafter promotion.Proof: the release succeeds and chat cards return
messageKeyplus typedparams, including numeric counts. No new environment variables, secrets, migrations, or backfills are required.Test evidence
dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~ProcessUserChatCommandHandlerTests|FullyQualifiedName~BulkUpdateHabitEmojisToolTests' --verbosity quietpassed all 254 unchanged tests despite the missing keys.Handle_EmojiToolCard_EmitsTranslationKeyAndNumericCountsran the real emoji tool for 1 and 185 habits.dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~Handle_EmojiToolCard_EmitsTranslationKeyAndNumericCounts' --verbosity quietfailed both cases becausemessageKeywas absent. A--no-build --verbosity normalrepeat confirmed that exact assertion. The missing contract had no existing assertion to strengthen, so this adds the production-path regression instead.dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~ProcessUserChatCommandHandlerTests|FullyQualifiedName~ToolResultMessagesTests|FullyQualifiedName~BulkUpdateHabitEmojisToolTests' --verbosity quiet.ActionContractSnapshot_OnlyAppendsOptionalFieldsasserts the complete legacy action JSON, omission of unset new fields, and deserialisation of both old and enriched JSON. The handler regression asserts numeric JSON params. Both generated OpenAPI documents retain the existing required action fields and schemas; only optional action properties and the params schema are added.dotnet build Orbit.slnx --verbosity quiet --disable-build-servers -m:1passed with 0 errors. A default parallel post-commit build had MSBuild child-worker termination (MSB4166); the serial retry passed. The earlier default solution build also passed.dotnet test tests/Orbit.Application.Tests --no-build --filter 'FullyQualifiedName~ToolResultMessagesTests|FullyQualifiedName~Handle_EmojiToolCard_EmitsTranslationKeyAndNumericCounts' --verbosity quietpassed 77 tests.dotnet test tests/Orbit.Domain.Tests --no-build --verbosity quietpassed 797 tests;dotnet test tests/Orbit.Application.Tests --no-build --verbosity quietpassed 5535 tests;dotnet test tests/Orbit.Analyzers.Tests --no-build --verbosity quietpassed 40 tests.dotnet test tests/Orbit.Infrastructure.Tests --no-build --verbosity quietpassed 3951 tests. All four test projects passed, with 10323 tests total and no failures or skips.All final checks ran against commit
4a9c29a0:dotnet build Orbit.slnxdotnet test tests/Orbit.Domain.Tests --no-build --no-restoredotnet test tests/Orbit.Application.Tests --no-build --no-restoredotnet test tests/Orbit.Infrastructure.Tests --no-build --no-restoreFocused verification passed 171 Application tests and 3 serializer/schema tests. Commit hooks and
git diff --checkpassed.Regression observations:
Before changes,
dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~ToolResultMessages --no-restorepassed all 75 existing tests, including the incorrect empty policy-params assertion. The strengthened tests, run with the implementation still defective, failed eight cases: missing policy reason, stale sanitized key, and six tool-error mappings.dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~RegistryWriteTools_EverySupportedActionHasNonFallbackKey --no-restorefailed whencreate_habit’s mapping was temporarily removed, identifying itscompletedfallback. The mapping was restored.dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~ToolResultSerialization --no-restorefailed both schema cases before correction because numeric fields advertised strings. All three cases pass afterward.The first broader Application run caught an overlapping-prefix crash for
Habit not found.inHandle_ToolCallWithFailure_ReturnsFailedAction. Added bare habit/goal regression cases failed before correction. The final focused command passed all 171 cases:The schema investigation checked the OpenAPI generator source, the installed runtime’s nullable schema-export path, and the library’s schema properties. Both generated documents now emit
{"type":["null","integer"],"format":"int32"}forcount,totalCount,skippedCount,weekStartDay, andhours.Comparison with
redesign/mainconfirmed thatToolResultMessageParamsis new and every pre-existingActionResultproperty remains unchanged.Interface evidence
No new runtime field is read from an external interface.
applied_count,total_matched,skipped_countandpartialcome from this repository'sBulkUpdateHabitsTool.BuildResult; formatter tests call that actual producer.BulkCreateResultand the tool argument schemas are repository-owned types.AgentOperationResponseFactoryalready carriesToolResult.PayloadandEntityNamethrough unchanged. The emoji regression uses the actual tool and asserts JSON fromChatStreamEvent.Final(...).ToJson().Full message key list for #24
These are the exact message keys for the consumer dictionaries. Missing optional params are omitted, and an action with no interpolation data sends
{}. Counts are raw JSON numbers for client pluralisation. Complete and partial bulk keys include the full summary, including skipped counts and completion state. Entity names and referral values are user data and are not translated. No new input feature or domain mutation is introduced, so no new validator or entity guard is needed.chat.toolResult.accountDeletionConfirmedchat.toolResult.accountResetchat.toolResult.aiSummary.disabledchat.toolResult.aiSummary.enabledchat.toolResult.allNotificationsDeletedchat.toolResult.allNotificationsReadchat.toolResult.apiKeyCreatedentityName?: stringchat.toolResult.apiKeyRevokedchat.toolResult.assignedTagsentityName?: stringchat.toolResult.billingPortalCreatedchat.toolResult.calendarAutoSync.disabledchat.toolResult.calendarAutoSync.enabledchat.toolResult.calendarImportDismissedchat.toolResult.calendarSuggestionDismissedchat.toolResult.calendarSyncRequestedchat.toolResult.checklistTemplateDeletedchat.toolResult.checkoutCreatedchat.toolResult.clarificationLimitExceededchat.toolResult.clockFormatSethours: integerchat.toolResult.completedchat.toolResult.confirmationRequiredchat.toolResult.createdChecklistTemplateentityName?: stringchat.toolResult.createdGoalentityName?: stringchat.toolResult.createdHabitentityName?: stringchat.toolResult.createdHabitscount,totalCount: integerschat.toolResult.createdSubHabitentityName?: stringchat.toolResult.createdTagentityName?: stringchat.toolResult.deletedGoalentityName?: stringchat.toolResult.deletedHabitentityName?: stringchat.toolResult.deletedHabitscount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.deletedHabitsPartialcount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.deletionCodeRequestedchat.toolResult.duplicatedHabitentityName?: stringchat.toolResult.failedchat.toolResult.featureFlagsUpdatedchat.toolResult.languageSetvalue: stringchat.toolResult.linkedGoalsToHabitentityName?: stringchat.toolResult.linkedHabitsToGoalentityName?: stringchat.toolResult.loggedHabitentityName?: stringchat.toolResult.loggedHabitscount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.loggedHabitsPartialcount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.movedHabitentityName?: stringchat.toolResult.movedHabitParententityName?: stringchat.toolResult.needsClarificationchat.toolResult.notificationDeletedchat.toolResult.notificationReadchat.toolResult.notificationsDeletedcount: integerchat.toolResult.onboardingCompletedchat.toolResult.policyDeniedchat.toolResult.proactiveAstra.disabledchat.toolResult.proactiveAstra.enabledchat.toolResult.pushSubscribedchat.toolResult.pushUnsubscribedchat.toolResult.referralCodevalue: stringchat.toolResult.reorderedGoalscount: integerchat.toolResult.reorderedHabitscount: integerchat.toolResult.repeatedSingleEntitytoolName,bulkTool: stringschat.toolResult.rescheduledHabitscount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.rescheduledHabitsPartialcount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.skippedHabitentityName?: stringchat.toolResult.skippedHabitscount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.skippedHabitsPartialcount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.suggestedBreakdownentityName?: stringchat.toolResult.supportRequestSentchat.toolResult.tagDeletedchat.toolResult.testPushRequestedchat.toolResult.themePreferenceUpdatedchat.toolResult.timezoneSetvalue: stringchat.toolResult.tourCompletedchat.toolResult.tourResetchat.toolResult.undidSkipHabitentityName?: stringchat.toolResult.unexpectedErrorchat.toolResult.unknownTooltoolName: stringchat.toolResult.unsupportedByPolicychat.toolResult.updatedChecklistentityName?: stringchat.toolResult.updatedEmojiscount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.updatedEmojisPartialcount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.updatedGoalentityName?: stringchat.toolResult.updatedGoalProgressentityName?: stringchat.toolResult.updatedGoalStatusentityName?: stringchat.toolResult.updatedHabitentityName?: stringchat.toolResult.updatedHabitscount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.updatedHabitsPartialcount,totalCount,skippedCount: integers;partial: booleanchat.toolResult.updatedTagentityName?: stringchat.toolResult.weekStartDaySetweekStartDay: integerKey list
All keys below use the
chat.toolResult.prefix.intmeans a JSON integer;?means the parameter can be omitted.nonemeans{}. Legacy text remains inentityNameorerror.