Repository navigation
Persist encrypted Astra conversations with bounded retention - #800
Conversation
There was a problem hiding this comment.
Caution
The new bulk user update breaks normal PostgreSQL account resets with a persistent concurrency conflict, even while conversation recording is disabled.
Reviewed changes Reviewed the complete persistence feature, chat capture paths, retention scheduling, export compatibility, and account lifecycle changes.
- Encrypted storage: Adds paired user/assistant conversation rows, encrypted content and tool arguments, indexes, and a disabled recording flag.
- Chat capture: Records final replies and original tool outcomes through a bounded background queue, including static block previews while excluding replays.
- Data lifecycle: Adds hourly retention cleanup, user-scoped exports, and reset-cutoff filtering under the existing user lock.
- Operational rollout: Documents disclosure prerequisites, capacity assumptions, activation, shutdown, and rollback behavior.
- Verification: All 62 focused tests passed across Application, Domain, and Infrastructure. Those tests use SQLite or mocks and do not exercise PostgreSQL
xmin; no live PostgreSQL execution was performed.
gpt-6.1-sol | 𝕏
There was a problem hiding this comment.
No new issues found.
Reviewed changes Re-reviewed the changes since 9bed59a1, with the complete PR diff as context, and confirmed that the resolved concurrency finding is fixed.
- Removed the conflicting update: Account cleanup no longer bulk-updates the tracked user, while the existing reset save persists the conversation cutoff atomically with deletion under the user lock.
- Added concurrency regression coverage: Exercised the real reset handler with a simulated advancing concurrency token, checking successful cleanup, persisted cutoff, and other-user isolation.
- Strengthened queued-write coverage: Verified that reset rejects previously queued conversation captures while allowing post-reset captures. All 17 focused reset and recorder tests passed; PostgreSQL execution and the full suite were not rerun in this review.
gpt-6.1-sol | 𝕏
…/ticket-201-persist-conversations
…/ticket-201-persist-conversations # Conflicts: # src/Orbit.Application/Chat/Commands/ProcessUserChatCommand.Accumulator.cs # src/Orbit.Application/Chat/Commands/ProcessUserChatCommand.cs # tests/Orbit.Application.Tests/Commands/Chat/ProcessUserChatCommandHandlerTests.cs
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Re-reviewed the merge from redesign/main since the prior approval at ff2f0f7a, using the complete PR diff as context.
- Preserved capture state: Retained conversation recording alongside the incoming habit-resolution accumulator fields and initialization, including block-answer recovery state.
- Retained failure coverage: Kept the diagnostic assertions for original tool arguments and errors together with the incoming failed-operation assertion.
- Verified merged behavior: Passed 311 focused Application tests and 20 focused Infrastructure tests covering chat, capture, streaming, exports, retention, migration shape, and account reset. The previous concurrency fix remains intact; the full suite and live PostgreSQL execution were not rerun.
gpt-6.1-sol | 𝕏
Keep input moderation, crisis replies and conversation diagnostics together. Regenerate PersistConversationTurns after the AI-budget migrations and assert the combined migration chain.
…/ticket-201-persist-conversations
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes Re-reviewed the changes since 80455eb9, using the complete PR diff as context.
- Preserved crisis capture: Enabled diagnostic recording on the early crisis-response path according to the recording flag, without invoking moderation, tools, or the model.
- Covered moderation boundaries: Added assertions for unrecorded normal refusals, recorded block-preparation refusals, and captured choice-answer text while retaining replay exclusion.
- Regenerated the migration: Positioned conversation storage after the incoming OAuth and AI-budget migrations and strengthened predecessor/target/current model checks to verify the intended schema additions.
- Verified merged behavior: Passed 355 focused Application tests and 36 focused Infrastructure tests, including recording, streaming, migration alignment, retention, and reset concurrency. The full suite and live PostgreSQL deployment were not rerun.
gpt-6.1-sol | 𝕏
|




Closes thomasluizon/orbit-tickets#201 (ORB-195).
Astra tool failures previously lost the prompt, arguments and original domain error. This adds a recent diagnostic conversation corpus without awaiting its database writes on the chat response path. Collection defaults to off pending the privacy disclosure owned by thomasluizon/orbit-tickets#104.
Change
ConversationTurn,OrbitDbContext, and the generatedPersistConversationTurnsmigration store user and assistant rows sharing a turn identifier. Content and tool-call JSON use the existing encryption converters. Indexes cover user/time, retention time, and turn/role; no content index is added.ProcessUserChatCommandandAstraBlockAnswerServicecapture ordered tool arguments and outcomes before response sanitization. Static block previews are captured and replayed answers are skipped. Full tool payloads are not copied.IConversationTurnRecorderis implemented by a 64-slot channel and a background consumer with fresh scopes and contexts per batch. Full queues, oversized captures, missing encryption and storage failures drop diagnostic records with warnings. Shutdown drains within five seconds. The existing awaited mutation saves remain in place. Conversation content never becomes a Hangfire job argument.ConversationRetentionJobruns hourly through the existing Hangfire registrar in both scheduler modes. Export adds an optional conversations section filtered by requesting user and retention cutoff. Account reset deletes rows and records a cutoff under the existing user lock so queued content cannot reappear; account deletion also has a cascading foreign key.infra/astra-conversation-retention.mdcontains the disclosure handoff, capacity scenario, operational gate, kill switch and rollback behavior. The window is 30 days; expired rows are purged hourly and exports exclude expired rows immediately. The planning scenario is 100 daily active users at 10 interactions/day, yielding 60,000 rows and about 234 MiB at 4 KiB/row before indexes and PostgreSQL overhead. These are assumptions, not measured traffic.The model, encryption, export, deletion and retention design also applies to main. Chat and static-block capture use the actual redesign/main paths required by this work order. No token telemetry or client behavior changes are included. The build regenerated both OpenAPI documents with no diff.
Retention, sizing and verbatim disclosure are also recorded in the ticket handoff comment.
Test evidence
dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~Handle_ProductionCreateHabitFailure_DoesNotExposeToolSchemaArguments' --no-restore --verbosity quietpassed the unchanged test, 1/1, despite no conversation store.dotnet ef migrations add PersistConversationTurns --project . --startup-project ../Orbit.Api --context OrbitDbContextfromsrc/Orbit.Infrastructure, using the design-time factory. Unit tests generate PostgreSQL Up/Down SQL and check model/snapshot alignment. PostgreSQL execution and a live migration round trip were not performed because the repository forbids a real-database harness. No production latency benchmark was run; the tests establish that recording does not await storage.dotnet build Orbit.slnx --verbosity quiet: exit 0, zero errors; OpenAPI regenerated without changes.dotnet test tests/Orbit.Domain.Tests --no-build --verbosity quiet: exit 0, 804/804.dotnet test tests/Orbit.Application.Tests --no-build --verbosity quiet: exit 0, 5,447/5,447.dotnet test tests/Orbit.Infrastructure.Tests --no-build --verbosity quietthen passed, exit 0, 3,948/3,948. Total full-suite result: 10,199 passing tests.dotnet test tests/Orbit.Infrastructure.Tests --filter 'FullyQualifiedName~ResetAccountRetryTests|FullyQualifiedName~AccountResetRepositoryTests' --no-restoreExit 0, 6 passed.
dotnet test tests/Orbit.Infrastructure.Tests --no-restore --filter 'FullyQualifiedName~Handle_ResetWithUserConcurrencyToken_CommitsDeletionsAndCutoffWithoutRetry'Exit 1,
DbUpdateConcurrencyExceptionafter retry exhaustion.dotnet test tests/Orbit.Infrastructure.Tests --no-build --no-restore --filter 'FullyQualifiedName~Handle_ResetWithUserConcurrencyToken_CommitsDeletionsAndCutoffWithoutRetry|FullyQualifiedName~Consumer_ResetDeletesRowsAndRejectsPreviouslyQueuedTurns'Exit 0, 2 passed.
dotnet build Orbit.slnx --no-restore: exit 0, zero errors. Regenerated OpenAPI was unchanged.dotnet test tests/Orbit.Domain.Tests --no-build --no-restore: exit 0, 804 passed.dotnet test tests/Orbit.Application.Tests --no-build --no-restore: exit 0, 5,447 passed.dotnet test tests/Orbit.Infrastructure.Tests --no-build --no-restore: exit 0, 3,949 passed.All pre-commit checks passed.
All final commands exited 0:
dotnet build Orbit.slnx: zero errors.dotnet test tests/Orbit.Domain.Tests --no-restore: 810 passed.dotnet test tests/Orbit.Application.Tests --no-restore: 5,760 passed.dotnet test tests/Orbit.Infrastructure.Tests --no-restore: 4,095 passed.Focused checks passed: 307 handler tests and 26 conversation/migration tests, including
HasPendingModelChanges()false.Regression evidence:
Before strengthening, all 10 cases passed with the merge defect present. Adding recording assertions made four crisis cases fail because
TryRecordwas never called. After fixing the early crisis path, they passed within:dotnet test tests/Orbit.Application.Tests --no-build --filter FullyQualifiedName~ProcessUserChatCommandHandlerTestsLive PostgreSQL deployment was not exercised.
Interface evidence
No new external response fields are read. Tool capture consumes the repository-owned
AiToolCall,AiToolCallResultandAgentOperationResultproducers. The recorded model comes fromAI:Model, verified againstAiCompletionClient's primaryChatClientconstruction. Channel saturation, shutdown and EF conversion behavior are exercised against the installed runtime and provider rather than invented response fixtures. The database flag mechanism and 30-second cache TTL are confirmed inFeatureFlagService, with the disabled seed registered inOrbitDbContext.Assumptions
AppFeatureFlagsand account allowlists, as the final work-order decision requires, rather than introducing a parallel AppConfigs feature flag.xminconcurrency behavior using a SQLite token and update trigger instead of a live PostgreSQL harness.AppFeatureFlagsand existing queue instructions over the earlierAppConfigswording.DailyAiDollarBudgetMigrationTeststo assert the six conversation additions rather than retain its obsolete newest-migration assumption.Manual steps
infra/astra-conversation-retention.mdinorbit-ui-mobileunderprivacy.*inpackages/shared/src/i18n/en.jsonandpt-BR.jsonon both web and mobile. Verify the published policy in each language and surface.astra_conversation_recordingon through the admin Orbit MCP toolmanage_feature_flags(the/flagskill), listing the flag before and after, and record the activation time on the owning ticket. The flag cache TTL is 30 seconds. Then read newly writtenConversationTurns.ContentandToolCallsJsonrows on the Render Postgres database (read-only query) and confirm they are ciphertext.ConversationTurnsrow count andpg_total_relation_sizeon the Render Postgres database weekly with the capacity scenario (read-only queries). Shorten the retention window if growth exceeds the prediction.astra_conversation_recordingthroughmanage_feature_flags, then list it again to confirm both. Allowlisted accounts otherwise stay enabled. Collection stays off in this delivery.Rollback intentionally drops all diagnostic conversation data through the migration's Down operation. It is not a lossless revert.
None added by this fix.
20261020014306_PersistConversationTurnsafter20261020005707_PriceDailyAiBudgetsInUsd. Read both ids back from__EFMigrationsHistoryon the staging Render Postgres (read-only query).Encryption__Keyin the staging and production API env groups; no new variables or backfill.astra_conversation_recordingon throughmanage_feature_flags.