feat(meetings): calendar-triggered auto-join prompt + Meeting Assistant settings UI#3721
Conversation
…rompts Introduces the CoreNotificationCard component to handle core-originated notifications with action buttons, specifically for meeting auto-join prompts. This includes tests to ensure proper rendering, action dispatching, and error handling. Additionally, updates to the notification center to display these notifications at the top, along with necessary localization strings for various languages.
…CoreNotificationCard Updated the logging mechanism in CoreNotificationCard to use the debug library for better namespace management, aligning with project logging standards.
… CoreNotificationCard test Refactored the test for CoreNotificationCard to streamline the dispatch of the notificationReceived action into a single line for improved readability. Additionally, updated the German translation for actionError to enhance formatting consistency.
…tionality Introduces the Meeting Assistant settings panel, allowing users to configure auto-join policies, post-call summaries, listen-only modes, and transcript ingestion settings. Updates include new translations for various languages and integration into the settings route. Additionally, tests are added to ensure proper functionality and persistence of settings.
… integration This update introduces a new function to handle calendar meeting candidates, applying user-defined auto-join policies. It includes improvements to the event collection process, allowing for the extraction of meeting URLs from calendar events. Additionally, the heartbeat planner now forwards imminent meetings to the auto-join policy, ensuring a seamless user experience. Tests have been updated to verify the correct handling of meeting URLs and related notifications.
…ates and read notifications Added tests to verify button disabling during action RPCs and the visibility of unread notification indicators in the CoreNotificationCard. Updated the relative time calculation to ensure non-negative values. Improved logging in MeetingSettingsPanel for better debugging and added tests for successful persistence of settings.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughImplements Meeting Assistant PR-1 (calendar auto-join prompt) and PR-5 (settings UI). The backend gains meeting URL extraction in the heartbeat planner, a refactored calendar auto-join policy handler publishing ChangesMeeting Assistant: calendar auto-join, notification actions UI, and settings panel
Sequence Diagram(s)sequenceDiagram
participant Heartbeat as Heartbeat Planner
participant Collectors as extract_meeting_url_from_map
participant Calendar as handle_calendar_meeting_candidate
participant EventBus as DomainEvent Bus
participant Redux as Redux / NotificationSlice
participant Card as CoreNotificationCard
participant RPC as agent_meetings_notification_action
Heartbeat->>Collectors: collect_calendar_meetings(events)
Collectors-->>Heartbeat: PendingEvent { meeting_url: Some(...) }
Heartbeat->>Calendar: handle_calendar_meeting_candidate(meet_url, title)
alt AskEachTime policy
Calendar->>EventBus: publish MeetingSessionCreated
Calendar->>Redux: dispatch CoreNotificationEvent { actions: [join, skip, alwaysJoin] }
Calendar-->>Heartbeat: owns_notification = true
Redux-->>Card: render action buttons
Card->>RPC: openhuman.agent_meetings_notification_action { action_id, payload }
RPC-->>Card: ok
Card->>Redux: dispatch markRead + clearNotificationActions
else Always policy
Calendar->>EventBus: publish MeetingAutoJoinTriggered
Calendar->>Calendar: spawn auto_join_meeting(listen_only_default)
Calendar-->>Heartbeat: owns_notification = true
else Never policy
Calendar-->>Heartbeat: owns_notification = false
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45d6a77dd4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ficationCard Implemented tests to verify the behavior of action buttons in the CoreNotificationCard component. Added scenarios to ensure buttons are cleared after successful actions and retained when RPC calls fail. This enhances the reliability of user interactions with notifications and prevents duplicate actions.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
src/openhuman/config/ops/ui.rs (1)
221-232: ⚡ Quick winAdd debug diagnostics for the new meet-settings apply branches.
Line 221 onward introduces new state-transition branches, but this function still has no entry/exit/branch diagnostics. Please add structured debug logs around the new policy/flag updates so runtime behavior is traceable during settings incidents.
As per coding guidelines, “Include debug logging with verbose diagnostics on all new/changed flows...”.
Suggested patch
pub async fn apply_meet_settings( config: &mut Config, update: MeetSettingsPatch, ) -> Result<RpcOutcome<serde_json::Value>, String> { + tracing::debug!( + auto_orchestrator_handoff = ?update.auto_orchestrator_handoff, + auto_join_policy = ?update.auto_join_policy, + auto_summarize_policy = ?update.auto_summarize_policy, + listen_only_default = ?update.listen_only_default, + ingest_backend_transcripts = ?update.ingest_backend_transcripts, + "[config][meet] apply_meet_settings enter" + ); if let Some(enabled) = update.auto_orchestrator_handoff { config.meet.auto_orchestrator_handoff = enabled; } @@ if let Some(ingest) = update.ingest_backend_transcripts { config.meet.ingest_backend_transcripts = ingest; } + tracing::debug!( + auto_orchestrator_handoff = config.meet.auto_orchestrator_handoff, + auto_join_policy = ?config.meet.auto_join_policy, + auto_summarize_policy = ?config.meet.auto_summarize_policy, + listen_only_default = config.meet.listen_only_default, + ingest_backend_transcripts = config.meet.ingest_backend_transcripts, + "[config][meet] apply_meet_settings updated" + ); config.save().await.map_err(|e| e.to_string())?;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/config/ops/ui.rs` around lines 221 - 232, Add structured debug logging around the new meet-settings configuration updates starting at line 221. For each of the four conditional branches (auto_join_policy, auto_summarize_policy, listen_only_default, and ingest_backend_transcripts), insert debug log statements that capture and display the values being set to config.meet. These logs should provide verbose diagnostics about what configuration values are being applied so that runtime behavior can be traced during settings incidents, following the coding guideline to include debug logging on all new/changed flows.Source: Coding guidelines
app/src/components/settings/panels/MeetingSettingsPanel.tsx (1)
5-11: ⚡ Quick winImport
isTaurifrom the canonical module path.Based on learnings, settings panel components should import
isTaurifrom the canonical moduleapp/src/utils/tauriCommands/commonrather than the barrel export. Update the relative path to'../../../utils/tauriCommands/common'.📦 Proposed fix
import { useT } from '../../../lib/i18n/I18nContext'; import { - isTauri, type MeetAutoJoinPolicy, type MeetAutoSummarizePolicy, openhumanGetMeetSettings, openhumanUpdateMeetSettings, } from '../../../utils/tauriCommands'; +import { isTauri } from '../../../utils/tauriCommands/common'; import PanelPage from '../../layout/PanelPage';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/settings/panels/MeetingSettingsPanel.tsx` around lines 5 - 11, The import statement is currently importing `isTauri` from the barrel export `'../../../utils/tauriCommands'`, but it should be imported from the canonical module path instead. Update the import to bring `isTauri` from `'../../../utils/tauriCommands/common'` while keeping the other imports (`MeetAutoJoinPolicy`, `MeetAutoSummarizePolicy`, `openhumanGetMeetSettings`, `openhumanUpdateMeetSettings`) from the original barrel export path `'../../../utils/tauriCommands'`. This will require splitting the existing import statement into two separate import statements.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/notifications/CoreNotificationCard.tsx`:
- Around line 18-27: The relativeTime function contains hardcoded English time
unit labels ("s ago", "m ago", "h ago", "d ago") that will display in English
regardless of the user's locale. Refactor this function to use the i18n pattern
from the repository: import and use the useT() hook from
app/src/lib/i18n/I18nContext, replace each hardcoded time label string with a
call to t() using appropriate translation keys, and add corresponding
translation entries to the en.ts locale file and all other locale files in the
repository to support multiple languages.
- Line 5: The CoreNotificationCard component is importing and using callCoreRpc
directly to make core RPC calls, which violates project policy requiring all
frontend core RPC calls to go through the relay contract. Remove the direct
import of callCoreRpc from coreRpcClient and instead create or use an existing
typed command wrapper in app/src/utils/tauriCommands/* that invokes the
core_rpc_relay Tauri command. Replace the direct callCoreRpc call in the
component (around lines 68-71) with a call to this typed wrapper function,
ensuring that the relay path is used for all core RPC communication to maintain
centralized transport and auth handling.
In `@app/src/lib/i18n/fr.ts`:
- Around line 3178-3182: The translation for the
'notifications.meeting.joinActive' key in the fr.ts file uses wording that
suggests sending a message rather than joining as an active participant. Replace
the current value 'Rejoindre et répondre' with 'Rejoindre en participant' to
make it clearer that the action is about joining as an active participant in the
meeting, not just responding with a message.
In `@app/src/lib/i18n/pt.ts`:
- Around line 3162-3166: The skip-button label for the meeting notification in
the notifications.meeting.skip key currently uses "Esta não" which reads like a
sentence fragment and creates an awkward notification action. Replace this value
with a short imperative label such as "Pular" or "Ignorar" to provide a clear,
direct action label for users.
In `@app/src/lib/i18n/ru.ts`:
- Line 3138: The translation for 'notifications.meeting.skip' uses 'Не эта'
which reads like a conversational reply rather than an action button label.
Replace this value with 'Пропустить' (which is the value used in common.skip) to
provide a clearer, more consistent button label that matches the rest of the UI
copy.
In `@src/openhuman/agent_meetings/calendar.rs`:
- Around line 170-180: When config loading fails in the error handling branch
(Err(e)), the code only publishes the legacy MeetAutoJoinPrompt event but does
not emit a CoreNotificationEvent with action buttons. Since the function returns
true, the heartbeat planner suppresses its plain notification, leaving no
actionable notification surface for the user. In addition to the existing
publish_global call for MeetAutoJoinPrompt, add another publish_global call in
this same error branch to emit the CoreNotificationEvent with appropriate action
buttons, ensuring users receive an actionable notification when config loading
fails.
- Around line 256-291: The code publishes a CoreNotificationEvent with action
buttons unconditionally after attempting store::create_session, even when the
session creation fails. Since the action handlers depend on the session existing
in the store, users can receive buttons that will fail against missing state.
Restructure the code so that the publish_global call and the entire
publish_core_notification block (including the action_payload setup and action
closure) only execute when store::create_session succeeds, not unconditionally
after the error is logged.
---
Nitpick comments:
In `@app/src/components/settings/panels/MeetingSettingsPanel.tsx`:
- Around line 5-11: The import statement is currently importing `isTauri` from
the barrel export `'../../../utils/tauriCommands'`, but it should be imported
from the canonical module path instead. Update the import to bring `isTauri`
from `'../../../utils/tauriCommands/common'` while keeping the other imports
(`MeetAutoJoinPolicy`, `MeetAutoSummarizePolicy`, `openhumanGetMeetSettings`,
`openhumanUpdateMeetSettings`) from the original barrel export path
`'../../../utils/tauriCommands'`. This will require splitting the existing
import statement into two separate import statements.
In `@src/openhuman/config/ops/ui.rs`:
- Around line 221-232: Add structured debug logging around the new meet-settings
configuration updates starting at line 221. For each of the four conditional
branches (auto_join_policy, auto_summarize_policy, listen_only_default, and
ingest_backend_transcripts), insert debug log statements that capture and
display the values being set to config.meet. These logs should provide verbose
diagnostics about what configuration values are being applied so that runtime
behavior can be traced during settings incidents, following the coding guideline
to include debug logging on all new/changed flows.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 280a0c02-cbe5-4659-81e7-b696684e52c9
📒 Files selected for processing (41)
app/src/components/notifications/CoreNotificationCard.test.tsxapp/src/components/notifications/CoreNotificationCard.tsxapp/src/components/notifications/NotificationCenter.tsxapp/src/components/settings/hooks/useSettingsNavigation.tsapp/src/components/settings/layout/settingsNavIcons.tsxapp/src/components/settings/panels/MeetingSettingsPanel.tsxapp/src/components/settings/panels/__tests__/MeetingSettingsPanel.test.tsxapp/src/components/settings/settingsRouteRegistry.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/lib/nativeNotifications/__tests__/service.test.tsapp/src/lib/nativeNotifications/service.tsapp/src/pages/Settings.tsxapp/src/store/notificationSlice.tsapp/src/test/setup.tsapp/src/utils/tauriCommands/config.test.tsapp/src/utils/tauriCommands/config.tssrc/core/event_bus/events.rssrc/core/event_bus/events_tests.rssrc/openhuman/agent_meetings/calendar.rssrc/openhuman/config/ops/ui.rssrc/openhuman/config/ops_tests.rssrc/openhuman/config/schemas/controllers.rssrc/openhuman/config/schemas/helpers.rssrc/openhuman/config/schemas/schema_defs.rssrc/openhuman/subconscious/heartbeat/planner/collectors.rssrc/openhuman/subconscious/heartbeat/planner/mod.rssrc/openhuman/subconscious/heartbeat/planner/persistence.rssrc/openhuman/subconscious/heartbeat/planner/types.rs
…uguese, and Russian Revised translations for the meeting notification actions to improve clarity. Updated 'joinActive' in French to 'Rejoindre en participant', 'skip' in Portuguese to 'Ignorar', and 'skip' in Russian to 'Пропустить'. This enhances the user experience by providing more accurate language in notifications.
…ith URLs Refined the test for extracting imminent calendar meetings to ensure that the is correctly populated and that the planner marks the event with . This change clarifies the expected behavior of the auto-join policy and improves the reliability of the test by focusing on the extraction logic without relying on external configurations.
This update introduces functions to extract and utilize the meeting owner's display name from calendar event payloads, improving the auto-join functionality. The function now accepts an optional owner display name, allowing for more accurate replies. Additionally, the notification handling logic has been updated to incorporate the reply anchor from action payloads, ensuring that the bot can respond appropriately based on the context of the meeting. Tests have been added to verify the correct behavior of these enhancements.
Code review — calendar-triggered auto-join + Meeting Assistant settingsWalkthrough. Adds a calendar-driven Meet auto-join flow: the heartbeat planner forwards imminent meetings to Changed-files summary (highlights)
Major1.
Net effect with AutoJoinPolicy::Always => {
if let Ok(Some(existing)) = store::get_session_by_meet_url(&config, &meet_url) {
if existing.status != MeetingSessionStatus::Ended {
tracing::debug!(meeting_id = %existing.id, "[meet:calendar] already auto-joined — skipping");
return false;
}
}
// ...create a session, then spawn auto_join_meeting...
}Major (pre-existing, now load-bearing)2. Composio path if let Some(loc) = root.get("location").and_then(|v| v.as_str()) {
if is_meeting_url(loc) {
return Some(loc.to_string()); // returns "Zoom Meeting: https://zoom.us/j/123"
}
}That string becomes the Minor / nitpicks3. 4. Questions
Looks good
Read-only review — no changes pushed. |
sanil-23
left a comment
There was a problem hiding this comment.
Requesting changes — gated on the correctness items from my detailed review.
Must fix
Alwayspolicy duplicatebot:join(agent_meetings/calendar.rs,Alwaysbranch). No per-meeting dedup, unlikeAskEachTime. The same meeting reaches the handler from twoallow_externalheartbeat stages (final_call+starting_now,planner/plan.rs:34-57) and the live Composio subscriber, each with a freshcorrelation_id— so a user on "Always join" can have the bot join the same call 2+ times. Mirror the AskEachTime session dedup before spawning the join.
Should fix (broken path this PR introduces buttons for)
2. Composio extract_meet_url leaks free-form location strings (calendar.rs:574-579). Returns the whole location (e.g. "Zoom Meeting: https://zoom.us/j/123"), which becomes the meetUrl in the AskEachTime action payload and then fails validate_meeting_url on click. collectors.rs already added extract_meeting_url_from_text for exactly this case — reuse it here.
Nitpicks #3 (triplicated MEETING_HOST_PATTERNS/is_meeting_url) and #4 (trailing . not trimmed) are non-blocking.
Otherwise the feature is well-structured and well-tested — fail-closed fallbacks, the listen-only safety rule, double-click guards, strict RPC enum parsing, and full i18n parity all look good. Happy to re-review once #1 is addressed.
graycyrus
left a comment
There was a problem hiding this comment.
@YellowSnnowmann sanil-23's CHANGES_REQUESTED is blocking and I'm deferring to it — both items are real correctness bugs that need to be addressed first:
-
The
Alwayspolicy has no per-meeting session dedup, unlikeAskEachTime. With two heartbeat stages (final_call+starting_now) and the live Composio subscriber each firing independently with freshcorrelation_idvalues, a user on Always can end up with the bot joining the same meeting 2-3 times. The fix is the same session-by-meet-url guard thatAskEachTimealready uses before spawning the join. -
The Composio
extract_meet_urlpath (the old subscriber branch incalendar.rs) still returns the rawlocationstring rather than extracting a URL out of it.collectors.rsin this very PR addedextract_meeting_url_from_textexactly for this — that function should be reused here, otherwise free-formlocationfields like"Zoom Meeting: https://zoom.us/j/123"become themeetUrlin the AskEachTime payload and then fail validation when the user actually clicks Join.
Please address those two before I do a full approval pass.
One additional issue I found that neither sanil-23 nor CodeRabbit flagged:
[major] MeetingSettingsPanel: no state rollback on persist failure
All four handlers (handleAutoJoinChange, handleAutoSummarizeChange, handleListenOnlyChange, handleIngestChange) use an optimistic-update pattern — local state updates immediately, then persist() fires. If the RPC fails, the error note appears but the component state is not restored. The select/toggle shows the new value while the backend still holds the old one. A user who notices the error and doesn't manually re-change the setting is left with a UI that lies about their configuration.
Fix: capture the prior value and restore it in the catch:
const handleAutoJoinChange = (next: MeetAutoJoinPolicy) => {
const prev = autoJoin;
setAutoJoin(next);
void persist({ auto_join_policy: next }).catch(() => setAutoJoin(prev));
};Same pattern for the other three handlers.
Two minor notes (non-blocking):
MeetingSessionCreated.sourceis a free-form string. The three valid values ("calendar","manual","api") live only in a comment — aCalendarSessionSourceenum would make this compile-time safe.MEETING_HOST_PATTERNSmissesteams.live.com(Teams Free / Teams Personal). Not blocking, easy follow-on.
Apart from the above, the implementation is genuinely well-structured: the AskEachTime session dedup, the listen-only fallback when no reply anchor resolves, the double-click guard and action-clear in CoreNotificationCard, the sequenced persistSeqRef in the settings panel, the full 14-locale i18n coverage, and the pure helper functions (build_notification_join_map, build_action_payload, extract_meeting_url_from_text) that make the logic unit-testable without live infrastructure — all of these are done right. Good work on the overall feature shape.
|
Addressed all four items from sanil-23's review (pending commit + push): Major #1 — Always policy dedup: Added Major #2 — Minor #3 — MEETING_HOST_PATTERNS triplicated: Intentionally deferred as a follow-up refactor — consolidating across Minor #4 — New tests cover: free-form label+URL extraction, paren/bracket stripping, trailing-period stripping, non-meeting locations, and the integration-level |
…ity on persist failure This update modifies the MeetingSettingsPanel to include rollback functionality for auto-join, auto-summarize, listen-only, and transcript ingestion settings when the persist operation fails. The changes ensure that users' selections revert to their previous values in case of an error during the save process. Additionally, new tests have been added to verify this behavior, enhancing the reliability of the settings management.
|
I re-checked the current head (af621df) against the blocking review items. The two correctness blockers appear addressed in code now:\n\n- Always-policy duplicate joins: src/openhuman/agent_meetings/calendar.rs now checks store::get_session_by_meet_url(&config, &meet_url) before spawning �ot:join, and skips when an active non-ended session exists.\n- Composio free-form location: �xtract_meet_url now routes the location field through �xtract_meeting_url_from_text, returning only a parseable http(s) meeting URL token. There are tests for free-form Zoom and Teams location strings.\n\nCurrent CI on #3721 is green, including Rust Quality, Rust Core/Tauri Coverage, Rust E2E, Frontend Quality/Coverage, Playwright shards, and Coverage Gate. The only unresolved GraphQL thread I see is the MeetingSettingsPanel rollback comment, which the current head also addresses with rollback callbacks and tests per the author reply.\n\nRequesting a reviewer re-check on the stale CHANGES_REQUESTED state. |
Summary
heartbeat.notify_meetings.PendingEventnow carries a dedicatedmeeting_url(separate fromdeep_link, which may point at a calendar details page), extracted fromhangoutLink/conferenceData/location.auto_join_policy: Never → skip, Always → auto-join + transparency notification, AskEachTime →CoreNotificationCardwith Join (listen) / Join (active) / Skip / Always-join actions.openhuman.agent_meetings_notification_action({notification_id, action_id, payload} → {ok}) handling join / skip / always_join.MeetSettingsPatch.DomainEventsMeetingSessionCreated+MeetingAutoJoinTriggered; full i18n across all 14 locales.Problem
collect_calendar_meetingsstoredhtmlLinkindeep_linkwhen an event had both links, so the Meet URL was lost and the assistant could never act on an upcoming call. There was also no UI to control auto-join / auto-summarize / listen-only / transcript-ingestion behavior.Solution
collect_calendar_events_recursiveextracts the join URL viaextract_meeting_url_from_map(matches known meeting host patterns throughis_meeting_url) and populatesPendingEvent.meeting_url.agent_meetings/calendar.rsaddsauto_join_policy_owns_notification+handle_calendar_meeting_candidateto filter Meet meetings and route by policy.handle_notification_action(ops.rs) resolves join (listen-only + correlation_id), skip (session → Ended), always_join (flip config + join).CoreNotificationCardrenders localized action buttons (ACTION_LABEL_KEYS, primary/secondary styling) and dispatches overcallCoreRpc; actions threaded throughnativeNotifications/service.ts.MeetSettingsPatchextended withauto_join_policy/auto_summarize_policy/listen_only_default/ingest_backend_transcripts;MeetingSettingsPanelwired into settings route registry + nav. Defaultask_each_time— no silent joins.Submission Checklist
events_tests.rs,ops_tests.rs. Vitest:CoreNotificationCard.test.tsx(button states + read state + RPC dispatch),MeetingSettingsPanel.test.tsx(each toggle/radio reads+writes config),config.test.ts,nativeNotifications/service.test.ts.pnpm test:coverage+pnpm test:rustlocally to confirm.docs/TEST-COVERAGE-MATRIX.md(or markN/A).## Related.Closes #NNNNin## Related.Impact
ask_each_time; Always still posts a transparency notification. Listen-only configurable. No secrets/PII logged (namespaceddebug).Related
docs/TEST-COVERAGE-MATRIX.mdrows.Summary by CodeRabbit
Release Notes