Repository navigation
Fix confirmed bulk habit creation and preserve approval on failure - #702
Merged
Merged
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the complete PR diff and traced bulk creation through policy evaluation, confirmation storage, transaction ownership, and the API, chat, and MCP callers.
- Quantity defaults: Recurring bulk items and sub-habits default omitted quantities to
1; explicit integer quantities remain available to existing validation, and one-time items without a quantity retainnull. - Approval and write atomicity: Bulk create opts into
ITransactionalAiTool, placing confirmation consumption and habit writes in the same transaction; failed outcomes and exceptions roll back before failure responses and audits are produced. Existing per-item partial-success behavior remains unchanged. - Failure logging: Unexpected execution failures receive error-level logs with operation and correlation IDs while excluding original exception messages and inner exceptions from the logged wrapper.
- Regression verification: All 61 focused tests passed: 13 bulk-tool tests, 23 bulk-execution/executor tests, and 25 policy/confirmation-store tests. The executor suite passed on retry after an initial
Segmentation fault (core dumped); the full solution suite and PostgreSQL concurrency behavior were not rerun in this review.
gpt-6.1-sol | 𝕏
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Reviewed the changes since 82226cc4, with the full PR diff and the policy, confirmation-store, audit, and unit-of-work paths as context.
- Preserved transaction exception identity: Kept unexpected exceptions unchanged inside the transaction delegate so retry classification and timeout conversion remain available before failure logging and auditing.
- Isolated per-attempt policy state: Extracted policy/tool execution and failure handling, resetting the stored decision before each evaluation so failure audits cannot reuse a previous retry's shadow decision.
- Extracted exception types: Moved rollback and redacted logging exceptions into public types in separate files without changing their payload or redaction behavior.
- Verified regressions: Passed all 77 focused parser, executor, bulk-execution, unit-of-work, policy, and confirmation-store tests after serial dependency restore resolved initial test-command timeouts. The full solution suite and PostgreSQL concurrency behavior were not rerun in this review.
gpt-6.1-sol | 𝕏
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.




Confirmed daily bulk creates rejected an omitted quantity even though the tool schema promises a default of 1. The bulk tool now applies that default, and failed bulk execution rolls back confirmation consumption together with habit writes so the pending operation remains executable.
Closes thomasluizon/orbit-tickets#1215.
Implementation
BulkCreateHabitsTool.csnormalizes omitted quantities to 1 when a recurrence unit exists, including sub-habits. Explicit quantities remain subject to the existing validator and entity guards; one-time tasks keep a null quantity.IAiTool.csdeclares transaction participation for tools whose writes use the same unit of work and have no external side effects. Bulk create opts in.AgentOperationExecutor.csevaluates policy and executes participating tools inside the existing unit-of-work transaction, throughExecutePolicyAndToolAsync. Exceptions leave the transaction delegate with their original type, so the Npgsql retrying execution strategy and the unit of work's timeout conversion behave as before. A failed tool result escapes asToolOutcomeRollbackExceptionbefore its failure response and audit entry are produced. This preserves approval without manually restoring a token or risking a partial write on retry.OperationPolicyStatepassed to the execution method and reset before each attempt, so the audit records the shadow decision when evaluation completed and none when evaluation threw.ToolOutcomeRollbackExceptionandRedactedOperationExceptionare public types in their own files. The existing audit error remains available.BulkCreateHabitsToolTests.cscovers recurrence defaults, explicit quantities, one-time tasks, and sub-habits.BulkCreateAgentOperationTests.csuses the existing SQLite fixture with the real catalog, confirmation store, policy evaluator, MediatR validation pipeline, handler, repositories, and unit of work.Preview validation is excluded by the ticket's binding scope decision because its previewer exists only on
redesign/main. The carry into that branch belongs to thomasluizon/orbit-tickets#746.Test evidence
env -u LANG dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~BulkCreateHabitsToolTestspassed all 7 unchanged tests, includingValidHabitsWithSubHabits_ReportsSuccessCount, while the defect was present.env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~AgentExecutionAndSanitizerTestspassed all 18 unchanged tests.ConfirmedDailyItemsWithoutQuantity_CreateEveryItemWithQuantityOne, failed before the parser fix underenv -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~BulkCreateAgentOperationTests. The audit contained the required-quantity validation error for every one of the 12 items. After the fix, that command passed and verified 12 persisted daily habits with quantity 1 and a consumed confirmation.ValidationFailure_LeavesPendingOperationApprovableandPayGateFailure_LeavesPendingOperationApprovablebecauseConsumedAtUtcwas populated.FailureBeforeWrite_LogsSafeExceptionAndAllowsSuccessfulRetryfailed because no log was emitted. After the fix, all pass. Coverage also verifies rollback after habit writes, successful retry, replay rejection after success, and removal of private habit text from the logged exception.Review batch 1 (SonarCloud reliability C on new code)
SonarCloud flagged
csharpsquid:S2583atAgentOperationExecutor.cs:258-259(a null-initialised local read with?.after a closure assigned it) andcsharpsquid:S3871at:401and:408(private nested exception types). The batch removed the captured local and moved both exception types into their own public files.AgentOperationExecutor_FailureAuditRecordsOnlyCompletedPolicyEvaluationpins the shadow decision in the failure audit when policy evaluation completed and none when it threw.Review batch 2 (exception identity inside the transaction)
The orchestrator's review of batch 1 found that it wrapped every exception inside the transaction delegate, which hid transient database failures from the retrying execution strategy and timeout cancellations from the unit of work's
TimeoutExceptionconversion. The installed EF Core 10.0.12 unwraps onlyDbUpdateExceptionbeforeShouldRetryOn, and Npgsql 10.0.3 treatsTimeoutExceptionas retryable.env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~AgentOperationExecutor_TransactionDelegatePreservesOriginalExceptionfailed 2 cases on batch 1; both observed the wrapper instead of the original timeout or cancellation exception.env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter 'FullyQualifiedName~AgentExecutionAndSanitizerTests|FullyQualifiedName~UnitOfWorkTests|FullyQualifiedName~BulkCreate'passed 49 tests, including both regression cases and the retry audit tests.Full runs on the final head
env -u LANG dotnet build Orbit.slnxandenv -u LANG dotnet testboth passed: 0 build errors and 7,135 tests passed (32 analyzer, 645 domain, 3,816 application, 2,642 infrastructure).env -u LANG LC_ALL=en_US.UTF-8 dotnet build Orbit.slnxandenv -u LANG LC_ALL=en_US.UTF-8 dotnet testboth passed with the same 7,135 tests and 0 build errors.Assumptions
UnitOfWork.ExecuteInTransactionAsynccommits returned results.Exception.Data, because the unit of work replaces a timeout cancellation with a newTimeoutException, which would drop data attached to the original.Manual steps
thomasluizon/orbit-apiGitHub Actions,Release API,Run workflowfrommain, withenvironment=stagingandbranch=main. A successful deployment and the workflow's health and live-commit verification prove the API release took effect. In staging Astra, create three daily habits, approve and execute them, verify all three exist, then delete them.Release APIworkflow withenvironment=productionandbranch=main; verify the deployed commit and API health through that workflow.redesign/mainthrough thomasluizon/orbit-tickets#746.