Skip to content

refactor(messaging): persist session registry state as plans#4945

Open
sandl99 wants to merge 44 commits into
mainfrom
feat/messaging-plan-session-registry
Open

refactor(messaging): persist session registry state as plans#4945
sandl99 wants to merge 44 commits into
mainfrom
feat/messaging-plan-session-registry

Conversation

@sandl99

@sandl99 sandl99 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrate onboard session and sandbox registry messaging persistence from legacy flat fields to the manifest-backed messaging plan. This removes legacy conflict backfill paths and keeps channel config, active/disabled state, and rebuild/session resume behavior plan-driven.

Related Issue

Fixes #4947
Parent: #3896

Changes

  • Store session messaging state only as messagingPlan and registry messaging state only as messaging.plan.
  • Derive configured, active, disabled, and config values from plans across onboard, rebuild, inventory, doctor, status, and channel lifecycle flows.
  • Remove legacy conflict-detection backfill code and tests for messagingChannels, disabledChannels, and messagingChannelConfig.
  • Update unit, integration, and e2e fixtures to assert plan-backed registry/session state.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

Passed: npm run typecheck:cli, npm run test-size:check, npm run source-shape:check, npm run build:cli, shell syntax checks for edited e2e scripts, and focused Vitest coverage for the messaging/session/registry paths (23 files, 426 tests).

Attempted npm test, npx prek run --all-files, commit hooks, and push hooks. They fail in unrelated CLI tests: auto-pair timing in test/nemoclaw-start.test.ts, debug wrapper timeouts in test/secret-redaction.test.ts, policy disclosure timeout in test/policies.test.ts, and intermittent doctor gateway timeout tests in test/cli/doctor-gateway-token.test.ts.

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: San Dang sdang@nvidia.com

Summary by CodeRabbit

  • Refactor
    • Moved messaging from legacy channel arrays to a single schema-versioned messaging plan; registry and session now store structured messaging state.
    • Simplified conflict detection and channel state flows by removing probe/backfill paths and relying on stored plans.
  • Bug Fixes
    • Status/overlap reporting now computes overlaps from stored state in a best-effort, non-failing way.
  • Tests
    • Updated test fixtures and E2E scripts to use the new messaging plan format.

sandl99 and others added 30 commits June 7, 2026 11:49
Stores the compiled SandboxMessagingPlan in the onboard session so that
resume runs can restore the plan to env without re-running enrollment
hooks (token paste, QR pairing). Fixes the gap where the registry entry
lost its `messaging` field on rebuild because the plan was only held in
a process env var that didn't survive across process restarts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…el-setup

Exports readMessagingPlanFromEnv and writePlanToEnv from
messaging-channel-setup.ts (which already owns MessagingSetupApplier)
to keep src/lib/onboard.ts from growing. Collapses the one-name
messaging-channel-setup require into a single line to free headroom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…an persistence

- Extract parseSandboxMessagingPlan to messaging-plan-session.ts to
  keep onboard-session.ts growth under the monolith threshold
- Guard plan restoration with sandbox-name + agent identity check so
  stale plans from renamed sandboxes or agent switches are not reused
- Add three behavior assertions in sandbox.test.ts: fresh setup
  persists env plan to session; matching plan is restored to env on
  non-interactive resume; mismatched sandbox name skips restoration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tive resume

rebuild.ts stages an authoritative plan from the registry (which reflects
post-stop/-start channel mutations) before calling onboard --resume.
Previously, the session plan restoration was unconditionally overwriting
that staged plan, causing stopped channels to reappear as active after
rebuild.

Now the handler checks the env first: if a plan is already staged
(rebuild path), it is used as-is. The session plan is only restored when
the env is empty, covering the plain process-restart resume case this PR
was originally targeting.

Also adds a test asserting the rebuild-path preference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- On non-interactive resume, restore plan from registry (always current
  after stop/start/add/remove) instead of stale session snapshot;
  env-first priority preserved so rebuild.ts staging still wins

- In rebuild.ts, persist the staged plan to the session alongside
  messagingChannels/disabledChannels/messagingChannelConfig so the
  session is fully consistent during the rebuild window

- Add getChannelsFromPlan, getDisabledChannelsFromPlan, and
  getMessagingChannelConfigFromPlan helpers in messaging-plan-session.ts
  so the next PR can replace the three individual session fields with
  plan-derived reads

- Move MessagingHostStateApplier re-export to messaging-channel-setup
  and getRegistrySandboxMessagingPlan helper to keep onboard.ts net-neutral

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…itecture (phase 4a)

- Add src/lib/messaging/applier/conflict-detection.ts with all core logic:
  ConflictRegistryEntry minimal interface, createMessagingConflictProbe factory
  (consolidates the duplicated tri-state probe pattern from onboard.ts,
  policy-channel.ts, and status-command-deps.ts), plan-to-request helpers
  (getActiveChannelIdsFromPlan, getCredentialHashesFromPlan,
  planToConflictChannelRequests), plan-preferred/legacy-fallback entry
  resolution (resolveActiveChannelsFromEntry, resolveCredentialHashesFromEntry),
  and pure detection functions (findConflictsInEntries, detectAllOverlapsInEntries,
  backfillLegacyEntryChannels)

- Rewrite messaging-conflict.ts as a thin public adapter: no detection logic
  inside, all three public exports delegate to applier functions, re-exports
  createMessagingConflictProbe so callers don't need to import from applier
  directly; removes getChannelDef/getChannelTokenKeys import (stored hashes
  are self-describing — no channel-constant lookup needed)

- Update onboard.ts conflict-check block: remove makeConflictProbe() and the
  MESSAGING_CHANNELS/hashCredential block; use createMessagingConflictProbe
  with injected openshell deps and findChannelConflictsFromPlan from plan

- Add plan-path tests: planToConflictChannelRequests grouping and exclusions,
  findChannelConflictsFromPlan matching/disabled/no-hash cases, plan-only
  registry entries in findChannelConflicts and findAllOverlaps

Closes #4392

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Remove redundant createMessagingConflictProbe named import from
  messaging-conflict.ts (symbol is already re-exported on the line below;
  flagged by CodeQL unused-import rule)
- Fix checkGatewayLiveness in onboard.ts to use runOpenshell exit status
  instead of stdout length; a healthy gateway with no sandboxes returns
  empty output with status 0, which the previous check incorrectly treated
  as "down"

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Fix credentialHash gate: gate on credentialAvailable (set by the compiler
  for all real onboarding flows) instead of credentialHash (not yet populated),
  which previously caused the conflict check to be silently skipped for all
  manifest-plan onboarding; planToConflictChannelRequests now includes channels
  with credentialAvailable=true and no hash, falling through to unknown-token
  conservative detection
- Fix cross-channel hash contamination: getCredentialHashesFromPlan now accepts
  an optional channelId filter; conflictReasonForRequest and
  conflictReasonForPair use the new resolveChannelHashesFromEntry helper so
  Slack or Discord hashes cannot produce false positive unknown-token results
  when checking a Telegram request
- Fix legacy hash fallback: when a plan-backed entry exists but carries no
  credentialHash for the requested channel (migration in flight), fall back to
  providerCredentialHashes so matching-token detection is not silently lost
- Align active-channel semantics with plan-filter.ts: getActiveChannelIdsFromPlan
  now also checks channel.active && !channel.disabled, not only disabledChannels
- Split plan-driven tests into src/lib/messaging/applier/conflict-detection.test.ts
  to offset the messaging-conflict.test.ts monolith growth; adds WhatsApp no-op
  test, cross-channel isolation, credentialAvailable-gate, and legacy-fallback cases

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…e and populate credentialHash in plan bindings

Remove hardcoded PROVIDER_SUFFIXES/KNOWN_CHANNEL_IDS from conflict-detection.ts; backfillLegacyEntryChannels now receives the suffix map as an injected parameter. The adapter layer (messaging-conflict.ts) derives it from BUILT_IN_CHANNEL_MANIFESTS credentials so adding a channel to the manifest registry propagates automatically.

Also fix credential-binding-engine to hash the env var value into credentialHash when credentialAvailable is true, enabling matching-token conflict detection on the plan-driven path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove legacy providerCredentialHashes fallback from resolveChannelHashesFromEntry — entries without a plan now return {} and fall through to unknown-token (conservative). Fix PROVIDER_SUFFIXES to collect all credential suffixes per manifest rather than only credentials[0], so multi-credential channels like Slack probe all their provider names during backfill (channel active if any present). Split 473-line conflict-detection.test.ts monolith into conflict-detection-plan.test.ts (plan helpers) and conflict-detection-entry.test.ts (detection functions) to stay under the monolith growth threshold.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…omments

Reject the env-staged plan when its sandboxName does not match the current
sandbox being created, preventing a stale plan from a prior run from gating
or bypassing conflict detection for a different sandbox.

Update stale comments that described the credential-binding engine as not yet
emitting credentialHash — it does now. Rephrase to reflect the remaining
no-hash case (registry-only resume without a compiler re-run).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… test fixtures

Restore the providerCredentialHashes fallback in resolveChannelHashesFromEntry
for entries whose plan carries no channel hashes (e.g. registry-only resume
without a compiler re-run), preserving safety coverage during migration.

Fix tgChannel(false, true) → tgChannel(true, true) in disabled-channel test
cases so assertions isolate plan.disabledChannels as the only gate rather than
passing via the inactive-channel path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dings with active channels

Address PR Review Advisor findings on phase-4a conflict detection:

- resolveChannelHashesFromEntry now filters legacy providerCredentialHashes to
  only the providerEnvKey values declared for the requested channel in
  BUILT_IN_CHANNEL_MANIFESTS, preventing unrelated Slack hashes from producing
  false Telegram conflicts and vice versa. Unknown channels retain the full map.

- planToConflictChannelRequests now intersects credentialBindings with
  getActiveChannelIdsFromPlan so stale bindings for inactive or absent channels
  cannot generate false conflict requests.

- Fix misleading JSDoc on findChannelConflictsFromPlan: available bindings with
  no credentialHash are included (not excluded) and fall through to conservative
  unknown-token detection.

- Add regression tests: cross-channel legacy hash scoping, active-channel binding
  intersection (absent channel, active=false), and slackChannel fixture for
  plan test file.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…rison and add no-plan fallback

Address second round of PR Review Advisor findings:

- conflictReasonForRequest and conflictReasonForPair now use manifest-declared
  CHANNEL_CREDENTIAL_ENV_KEYS as the primary comparison set. For multi-credential
  channels like Slack (SLACK_BOT_TOKEN + SLACK_APP_TOKEN), a differing bot token
  with a missing app token previously returned null; it now returns unknown-token
  because the missing manifest key marks the comparison as incomplete.

- onboard.ts conflict gate: when hasPlanCredentials is false but token-backed
  channels were selected (enabledChannels contains non-QR channels), fall back to
  findChannelConflicts with just the channel names to preserve conservative
  unknown-token warnings even when the compiled plan has no credential data
  (e.g. credential-store-backed availability or stale env plan).

- policy-channel.ts: add explicit comment scoping the add-channel path as a
  known limitation — no compiled plan is available at channels-add time, so
  findChannelConflicts is used with caller-built hashes; plan-driven migration
  is follow-up work.

- Tests: four Slack partial-hash suppression cases covering the false-negative
  regression (conflictReasonForRequest with missing app token, both differ,
  conflictReasonForPair with missing app token from both sides, both differ).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…nifest-driven hashing

Address third round of PR Review Advisor findings:

- messaging-conflict.ts: add findChannelConflictsForOnboarding() that unions
  the plan-based hash-precise check with a channel-name-only fallback for any
  selectedTokenChannels not covered by the plan's credentialAvailable bindings.
  This ensures a partial or stale same-sandbox plan cannot silently skip channels
  it omits. onboard.ts now delegates to this single function rather than
  inlining the union logic.

- onboard.ts conflict gate: replace the multi-branch plan/fallback block with a
  single call to findChannelConflictsForOnboarding(sandboxName, currentPlan,
  selectedTokenChannels, registry), keeping all conflict logic in messaging-conflict.ts.

- policy-channel.ts checkChannelAddConflict: replace raw acquired-map iteration
  with manifest-driven hash building via createBuiltInChannelManifestRegistry().
  Iterates channelManifest.credentials[].providerEnvKey to build credentialHashes,
  mirroring planToConflictChannelRequests without requiring a compiled plan.
  QR-only channels (WhatsApp: credentials=[]) exit early with no conflict possible.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…tection

Legacy entries no longer carry hash metadata into conflict comparisons.
resolveChannelHashesFromEntry returns {} for entries without a plan, which
falls through to conservative unknown-token detection in all callers.

- ConflictRegistryEntry: drop providerCredentialHashes field
- resolveChannelHashesFromEntry: plan path only; legacy entries → {}
- CHANNEL_CREDENTIAL_ENV_KEYS comment updated (now solely for manifest-key
  comparison set, not legacy scoping)
- Section comment: "plan-preferred, legacy-fallback" → "Entry resolution"

- Remove findChannelConflictsForOnboarding and fallback logic; onboard.ts
  calls findChannelConflictsFromPlan directly (rationale in issue #4392)

Test updates:
- conflict-detection-entry.test.ts: remove legacy cross-channel hash scoping
  describe block; migrate Slack pair tests from providerCredentialHashes
  fixtures to plan-backed entries
- messaging-conflict.test.ts: remove hash-comparison tests (covered by
  plan-entry tests); update remaining fixtures to drop providerCredentialHashes;
  update file header comment

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…aths

Completes the providerCredentialHashes deprecation started in the phase
4a conflict-detection migration. The field was written by onboard.ts,
policy-channel.ts, and rebuild.ts, and read by messaging-credentials.ts
and workflow-planner.ts for rotation detection and credential-availability
inference.

All write paths are removed. Reads are replaced with plan-backed
equivalents: detectMessagingCredentialRotation now reads
credentialBindings.credentialHash from the compiled SandboxMessagingPlan,
and credentialAvailabilityFromSandboxEntry reads
plan.credentialBindings[].credentialAvailable. The field is removed from
SandboxEntry and MessagingWorkflowPlannerSandboxEntry entirely; existing
sandboxes.json files with stale providerCredentialHashes entries are
unaffected at runtime (unknown JSON fields are ignored).

Tests updated to use plan-backed fixtures for matching-token conflict
scenarios; credential-rotation tests use messaging.plan.credentialBindings
fixtures.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…-messaging

Both files shrank after removing providerCredentialHashes fixtures and
assertions in the preceding refactor commit.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Take nemoclaw-start budget reduction from main (5310→5300) and keep
onboard-messaging reduction from this branch (2122→2110).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Comment thread test/channels-add-preset.test.ts Fixed
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

E2E Advisor Recommendation

Required E2E: channels-add-remove-e2e, channels-stop-start-e2e, messaging-providers-e2e, rebuild-hermes-e2e, diagnostics-e2e, network-policy-e2e, onboard-resume-e2e
Optional E2E: token-rotation-e2e, openclaw-inference-switch-e2e, inference-routing-e2e, rebuild-openclaw-e2e

Dispatch hint: channels-add-remove-e2e,channels-stop-start-e2e,messaging-providers-e2e,rebuild-hermes-e2e,diagnostics-e2e,network-policy-e2e,onboard-resume-e2e

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • channels-add-remove-e2e (high): Required because policy-channel add/remove was rewritten around compiled messaging plans and gateway provider registration. This E2E exercises Telegram/Discord/Slack channel add/remove lifecycle, deferred rebuild behavior, gateway credential reuse, and the touched script itself.
  • channels-stop-start-e2e (high): Required because disabled/active channel state moved into the messaging plan and channel status/doctor now consume registry helper projections. This directly validates stop/start persistence across rebuild for OpenClaw and Hermes across multiple providers.
  • messaging-providers-e2e (high): Required because onboarding messaging credentials, messaging config, provider registration, placeholder rewrite, and WhatsApp QR/no-provider paths changed. This is the broadest real provider/credential/L7 proxy coverage for the affected messaging stack.
  • rebuild-hermes-e2e (high): Required because sandbox rebuild code and the Hermes rebuild E2E script were touched, and the plan-based messaging state must still regenerate Hermes config and survive rebuild/upgrade flows.
  • diagnostics-e2e (medium): Required because doctor, channels status, WhatsApp diagnostics, and status command dependencies changed. This validates real operator diagnostics against a live sandbox rather than only mocked registry projections.
  • network-policy-e2e (medium): Required because channel add/remove and onboard policy handlers affect network policy application/removal for messaging channels, and src/lib/policy/index.ts changed. This is security-boundary coverage.
  • onboard-resume-e2e (high): Required because onboard session schema/state and messaging plan session persistence changed. Resume coverage is needed to verify partially completed onboarding can recover with the new messagingPlan shape.

Optional E2E

  • token-rotation-e2e (medium): Useful adjacent credential confidence because messaging credential reuse/rotation and conflict detection changed, but channels-add-remove and messaging-providers cover the most direct provider paths.
  • openclaw-inference-switch-e2e (high): Optional because inference source files were not directly changed, but Session shape migration touched tests and onboard state used by runtime inference selection.
  • inference-routing-e2e (medium): Optional adjacent check for route/credential isolation after Session schema updates; not merge-blocking because the diff primarily changes messaging plan state rather than inference routing implementation.
  • rebuild-openclaw-e2e (high): Optional companion to required Hermes rebuild coverage; useful if capacity allows because the changed rebuild path and messaging plan projections also affect OpenClaw config generation.

New E2E recommendations

  • legacy-messaging-registry-migration (high): This PR removes broad direct use of legacy messagingChannels/disabledChannels and introduces plan-based projections, but there is no clearly named E2E that starts from an older real registry/session containing only legacy fields and verifies add/remove/doctor/rebuild migrate or preserve behavior correctly.
    • Suggested test: Add an E2E that seeds a legacy sandboxes.json/onboard session with messagingChannels, disabledChannels, and messagingChannelConfig, upgrades to the PR code, then runs channels status, channels start/stop, rebuild, and doctor to prove migration/backfill compatibility.
  • cross-sandbox-messaging-conflict-detection (medium): Conflict detection and backfill internals changed and channels add no longer performs the previous gateway-probe backfill path. Existing unit coverage is extensive, but a real CLI E2E with two sandboxes sharing a token would catch provider/registry drift and fail-closed behavior.
    • Suggested test: Add a focused E2E that creates two registered sandboxes, attempts to add the same token-backed channel to both, verifies non-interactive conflict aborts without leaking secrets, and verifies --force proceeds with explicit warning.
  • whatsapp-paused-channel-diagnostics (medium): WhatsApp channel status now skips deep probing when paused via messaging plan disabled state. Existing channels-stop-start is broad, but a dedicated diagnostic assertion would prevent regressions where status probes a deliberately torn-down bridge and reports a false failure.
    • Suggested test: Extend or add a channels status E2E that stops WhatsApp, rebuilds, runs channels status --channel whatsapp and doctor, and asserts the output reports paused/info without running or requiring the deep WhatsApp bridge probe.

Dispatch hint

  • Workflow: .github/workflows/nightly-e2e.yaml
  • jobs input: channels-add-remove-e2e,channels-stop-start-e2e,messaging-providers-e2e,rebuild-hermes-e2e,diagnostics-e2e,network-policy-e2e,onboard-resume-e2e

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

E2E Scenario Advisor Recommendation

Required scenario E2E: ubuntu-repo-cloud-openclaw, ubuntu-repo-cloud-openclaw-telegram, ubuntu-repo-cloud-openclaw-slack, ubuntu-repo-cloud-openclaw-token-rotation, ubuntu-rebuild-openclaw
Optional scenario E2E: ubuntu-repo-cloud-openclaw-discord, ubuntu-repo-cloud-hermes-slack

Dispatch required scenario E2E:

  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-telegram
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-slack
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-token-rotation
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-rebuild-openclaw

Workflow run

Full scenario advisor summary

E2E Scenario Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required scenario E2E

  • ubuntu-repo-cloud-openclaw: Core onboarding/session/registry/policy/status changes can affect the default repo-checkout OpenClaw scenario and its post-onboard smoke, inference, and credential contracts.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw
  • ubuntu-repo-cloud-openclaw-telegram: Messaging plan, channel add/status/doctor, policy preset, provider conflict-detection, and registry state changes need the primary single-token OpenClaw messaging scenario.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-telegram
  • ubuntu-repo-cloud-openclaw-slack: Slack covers the multi-credential messaging-plan/provider path affected by messaging credentials, reuse, workflow planner, and registry host-state changes.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-slack
  • ubuntu-repo-cloud-openclaw-token-rotation: Conflict-detection and messaging credential/reuse changes can affect provider rotation isolation, which is specifically exercised by this scenario.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-token-rotation
  • ubuntu-rebuild-openclaw: Rebuild, registry, onboard-session, policy, and messaging-plan persistence changes should be checked against the scenario rebuild lifecycle and state-preservation contracts.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-rebuild-openclaw

Optional scenario E2E

  • ubuntu-repo-cloud-openclaw-discord: Adjacent OpenClaw messaging scenario using the same common provider/placeholder/no-secret checks on a different bridge; useful if broader messaging confidence is desired.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-discord
  • ubuntu-repo-cloud-hermes-slack: Optional cross-agent coverage for the changed generic messaging-plan and registry paths on Hermes with a multi-token Slack channel.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-hermes-slack

Relevant changed files

  • src/lib/actions/sandbox/channel-status.ts
  • src/lib/actions/sandbox/doctor.ts
  • src/lib/actions/sandbox/policy-channel.ts
  • src/lib/actions/sandbox/rebuild.ts
  • src/lib/inventory/index.ts
  • src/lib/messaging/applier/conflict-detection/backfill.ts
  • src/lib/messaging/applier/conflict-detection/entries.ts
  • src/lib/messaging/applier/conflict-detection/index.ts
  • src/lib/messaging/applier/conflict-detection/probe.ts
  • src/lib/messaging/applier/conflict-detection/types.ts
  • src/lib/messaging/compiler/workflow-planner.ts
  • src/lib/onboard.ts
  • src/lib/onboard/channel-state.ts
  • src/lib/onboard/machine/events.ts
  • src/lib/onboard/machine/handlers/policies.ts
  • src/lib/onboard/machine/handlers/sandbox.ts
  • src/lib/onboard/messaging-config.ts
  • src/lib/onboard/messaging-credentials.ts
  • src/lib/onboard/messaging-plan-session.ts
  • src/lib/onboard/messaging-reuse.ts
  • src/lib/onboard/session-updates.ts
  • src/lib/policy/index.ts
  • src/lib/sandbox/whatsapp-diagnostics.ts
  • src/lib/state/onboard-session.ts
  • src/lib/state/registry-messaging.ts
  • src/lib/state/registry.ts
  • src/lib/status-command-deps.ts

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor

Findings: 0 needs attention, 7 worth checking, 0 nice ideas
Since last review: 1 prior item resolved, 3 still apply, 1 new item found

Review findings

🛠️ Needs attention

  • None.

🔎 Worth checking

  • Source-of-truth review needed: Persisted messaging plan validation boundary: The advisor marked localized patch analysis as missing.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: parseSandboxMessagingPlan() and assertSandboxMessagingPlan() check top-level shape only while applyCredentialsAtOpenShell(), applyPolicyAtOpenShell(), applyAgentConfigAtOpenShell(), and applyPlanToRegistry() consume nested fields.
  • Source-of-truth review needed: Messaging registry/session clean cutover: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: registry-messaging helpers return empty arrays without a plan; resolveActiveChannelsFromEntry() returns null without a plan; findConflictsInEntries() filters to e.messaging?.plan != null; backfill/probe code is deleted.
  • Source-of-truth review needed: WeChat host-QR metadata special case: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: hydrateAddChannelEnvFromSession(), persistWechatConfigFromEnv(), and rebuild WeChat seeding remain outside the generic manifest-plan boundary.
  • Persisted messaging plans are shallowly validated before security-sensitive side effects (src/lib/onboard/messaging-plan-session.ts:7): The PR makes env/session/registry SandboxMessagingPlan objects the durable source for provider upserts, policy application, sandbox config rendering, rebuild decisions, and registry persistence. The decode/parse checks only broad top-level shape, so a corrupted or tampered persisted plan can carry unexpected channel IDs, provider names/env keys, network policy entries, render entries, hooks, build/state updates, or health checks toward side-effect appliers.
    • Recommendation: Validate decoded and persisted plans against the built-in manifest registry before storing or applying them, or regenerate side-effect plans from manifests at each boundary. Check channel IDs, sandbox/agent identity, credential bindings, provider env keys/provider names, policy presets/keys, render targets, build/state updates, hooks, and health checks. Add negative tests proving tampered plans are rejected before provider, policy, render, hook, or registry calls.
    • Evidence: parseSandboxMessagingPlan() validates schemaVersion, sandboxName, agent, workflow, and top-level arrays/objects only. MessagingSetupApplier.assertSandboxMessagingPlan() has the same broad checks, while applyCredentialsAtOpenShell() consumes plan.credentialBindings, applyPolicyAtOpenShell() consumes plan.networkPolicy.entries, applyAgentConfigAtOpenShell() consumes plan.agentRender and hook outputs, and MessagingHostStateApplier.applyPlanToRegistry() persists cloned plans.
  • Clean cutover silently ignores legacy-only messaging state (src/lib/messaging/applier/conflict-detection/entries.ts:128): The linked issue explicitly allows removing compatibility fallback/backfill for old messagingChannels, messagingChannelConfig, and disabledChannels, so the clean cutover is in scope. The runtime behavior is still silent: legacy-only on-disk entries look like they have no channels in status, doctor, inventory, channel lifecycle, and conflict paths. That can hide active bridges and suppress conservative duplicate-token warnings for single-consumer messaging channels.
    • Recommendation: Add explicit runtime or release-note guidance for legacy-only messaging state, or document and test the intentional no-channel behavior. In conflict-sensitive paths, consider failing closed with migration guidance when legacy-only fields are present without messaging.plan, while still avoiding compatibility backfill.
    • Evidence: findConflictsInEntries() filters other sandboxes to entries with e.messaging?.plan != null. resolveActiveChannelsFromEntry() returns null without a plan. registry-messaging helpers return empty arrays without entry.messaging.plan. The PR deletes conflict-detection backfill/probe code and removes the backfill call from policy-channel.ts.
  • WeChat host-QR metadata remains split outside the manifest plan boundary (src/lib/actions/sandbox/policy-channel.ts:856): WeChat account/baseUrl/userId state remains special-cased in channel lifecycle and rebuild glue instead of being owned wholly by manifest hooks or plan state. During a source-of-truth migration, this split state makes deferred rebuild and resume behavior harder to reason about and can drift from the plan-backed channel model.
    • Recommendation: Move WeChat metadata hydration and persistence behind manifest hooks or the plan-owned state boundary, or document it as a temporary compatibility path with the invalid state handled, source boundary, regression test, and removal condition.
    • Evidence: hydrateAddChannelEnvFromSession() reads session.wechatConfig into process.env and persistWechatConfigFromEnv() writes WeChat metadata back to the onboard session. Rebuild also continues WeChat seed/hydration behavior, while the WeChat manifest already declares state/rebuild hydration concepts.
  • Channel lifecycle can report success even if plan persistence fails (src/lib/actions/sandbox/policy-channel.ts:1070): Several channel lifecycle paths call MessagingHostStateApplier.applyPlanToRegistry() for durable plan state but do not check the boolean result. If the sandbox entry is missing, the plan sandboxName mismatches, or the registry update fails, the command can still report the channel as enabled/removed and queue a rebuild after provider or policy side effects have already run, leaving gateway/policy state out of sync with durable registry state.
    • Recommendation: Check the return value anywhere applyPlanToRegistry() is used in add/remove lifecycle paths. If persistence fails, stop before reporting success or rebuilding; where earlier side effects already happened, rollback or print explicit recovery guidance.
    • Evidence: The in-sandbox QR add branch and token-backed add branch call MessagingHostStateApplier.applyPlanToRegistry(sandboxName, plan) without checking the result. persistManifestChannelRemovePlan() similarly ignores applyPlanToRegistry() failure.

🌱 Nice ideas

  • None.
Consider writing more tests for
  • **Runtime validation** — Tampered persisted messagingPlan with unknown channelId is rejected before registry persistence or side-effect appliers run.. The refactor spans sandbox lifecycle, OpenShell provider credentials, policy application, registry/session persistence, status/doctor diagnostics, QR cleanup, rebuild, and shell e2e fixtures. Positive plan-backed coverage was updated, but runtime/negative validation remains advisable for tampered persisted plans, legacy-only no-plan behavior, and registry persistence failure paths.
  • **Runtime validation** — Tampered credential binding with undeclared providerEnvKey/providerName is rejected before OpenShell provider create/update.. The refactor spans sandbox lifecycle, OpenShell provider credentials, policy application, registry/session persistence, status/doctor diagnostics, QR cleanup, rebuild, and shell e2e fixtures. Positive plan-backed coverage was updated, but runtime/negative validation remains advisable for tampered persisted plans, legacy-only no-plan behavior, and registry persistence failure paths.
  • **Runtime validation** — Tampered networkPolicy presetName or policyKeys not produced by the built-in manifest is rejected before policy apply.. The refactor spans sandbox lifecycle, OpenShell provider credentials, policy application, registry/session persistence, status/doctor diagnostics, QR cleanup, rebuild, and shell e2e fixtures. Positive plan-backed coverage was updated, but runtime/negative validation remains advisable for tampered persisted plans, legacy-only no-plan behavior, and registry persistence failure paths.
  • **Runtime validation** — Tampered agentRender entry for an undeclared channel or manifest target is rejected before sandbox config write.. The refactor spans sandbox lifecycle, OpenShell provider credentials, policy application, registry/session persistence, status/doctor diagnostics, QR cleanup, rebuild, and shell e2e fixtures. Positive plan-backed coverage was updated, but runtime/negative validation remains advisable for tampered persisted plans, legacy-only no-plan behavior, and registry persistence failure paths.
  • **Runtime validation** — Tampered hook/build-file request not declared by the built-in manifest is rejected before hook execution or sandbox file write.. The refactor spans sandbox lifecycle, OpenShell provider credentials, policy application, registry/session persistence, status/doctor diagnostics, QR cleanup, rebuild, and shell e2e fixtures. Positive plan-backed coverage was updated, but runtime/negative validation remains advisable for tampered persisted plans, legacy-only no-plan behavior, and registry persistence failure paths.
  • **Acceptance clause:** Phase 4c of Refactor messaging integrations into a manifest-first planning architecture #3896 migrates onboard-session and sandbox registry messaging state to the manifest-backed `SandboxMessagingPlan`. — add test evidence or identify existing coverage. Session now stores messagingPlan and SandboxEntry now stores messaging.plan; registry/session tests cover plan persistence. The remaining gap is that persisted plans are accepted with shallow validation before manifest-sensitive side effects.
  • **Acceptance clause:** Update onboard, rebuild, status, doctor, inventory, and channel lifecycle flows to derive channel config, active channels, and disabled channels from plans. — add test evidence or identify existing coverage. onboard handlers, rebuild, status-command-deps/inventory, doctor/channel-status, and policy-channel now read plan-backed helpers. Coverage exists for many positive plan-backed paths, but tampered-plan and legacy-only no-plan behavior are not fully covered.
  • **Acceptance clause:** Make this a clean cutover: no compatibility fallback or legacy backfill is required for `messagingChannels`, `messagingChannelConfig`, or `disabledChannels`. — add test evidence or identify existing coverage. The code implements no fallback: helpers return empty/null without messaging.plan and conflict detection skips no-plan entries. The remaining concern is that this is silent and lacks targeted status/doctor/conflict/channel-lifecycle tests or guidance for legacy-only state.
Since last review details

Current findings:

  • Source-of-truth review needed: Persisted messaging plan validation boundary: The advisor marked localized patch analysis as missing.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: parseSandboxMessagingPlan() and assertSandboxMessagingPlan() check top-level shape only while applyCredentialsAtOpenShell(), applyPolicyAtOpenShell(), applyAgentConfigAtOpenShell(), and applyPlanToRegistry() consume nested fields.
  • Source-of-truth review needed: Messaging registry/session clean cutover: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: registry-messaging helpers return empty arrays without a plan; resolveActiveChannelsFromEntry() returns null without a plan; findConflictsInEntries() filters to e.messaging?.plan != null; backfill/probe code is deleted.
  • Source-of-truth review needed: WeChat host-QR metadata special case: The advisor marked localized patch analysis as needs_followup.
    • Recommendation: Identify the invalid state, source boundary, source-fix constraint, regression test, and removal condition before merging the localized behavior.
    • Evidence: hydrateAddChannelEnvFromSession(), persistWechatConfigFromEnv(), and rebuild WeChat seeding remain outside the generic manifest-plan boundary.
  • Persisted messaging plans are shallowly validated before security-sensitive side effects (src/lib/onboard/messaging-plan-session.ts:7): The PR makes env/session/registry SandboxMessagingPlan objects the durable source for provider upserts, policy application, sandbox config rendering, rebuild decisions, and registry persistence. The decode/parse checks only broad top-level shape, so a corrupted or tampered persisted plan can carry unexpected channel IDs, provider names/env keys, network policy entries, render entries, hooks, build/state updates, or health checks toward side-effect appliers.
    • Recommendation: Validate decoded and persisted plans against the built-in manifest registry before storing or applying them, or regenerate side-effect plans from manifests at each boundary. Check channel IDs, sandbox/agent identity, credential bindings, provider env keys/provider names, policy presets/keys, render targets, build/state updates, hooks, and health checks. Add negative tests proving tampered plans are rejected before provider, policy, render, hook, or registry calls.
    • Evidence: parseSandboxMessagingPlan() validates schemaVersion, sandboxName, agent, workflow, and top-level arrays/objects only. MessagingSetupApplier.assertSandboxMessagingPlan() has the same broad checks, while applyCredentialsAtOpenShell() consumes plan.credentialBindings, applyPolicyAtOpenShell() consumes plan.networkPolicy.entries, applyAgentConfigAtOpenShell() consumes plan.agentRender and hook outputs, and MessagingHostStateApplier.applyPlanToRegistry() persists cloned plans.
  • Clean cutover silently ignores legacy-only messaging state (src/lib/messaging/applier/conflict-detection/entries.ts:128): The linked issue explicitly allows removing compatibility fallback/backfill for old messagingChannels, messagingChannelConfig, and disabledChannels, so the clean cutover is in scope. The runtime behavior is still silent: legacy-only on-disk entries look like they have no channels in status, doctor, inventory, channel lifecycle, and conflict paths. That can hide active bridges and suppress conservative duplicate-token warnings for single-consumer messaging channels.
    • Recommendation: Add explicit runtime or release-note guidance for legacy-only messaging state, or document and test the intentional no-channel behavior. In conflict-sensitive paths, consider failing closed with migration guidance when legacy-only fields are present without messaging.plan, while still avoiding compatibility backfill.
    • Evidence: findConflictsInEntries() filters other sandboxes to entries with e.messaging?.plan != null. resolveActiveChannelsFromEntry() returns null without a plan. registry-messaging helpers return empty arrays without entry.messaging.plan. The PR deletes conflict-detection backfill/probe code and removes the backfill call from policy-channel.ts.
  • WeChat host-QR metadata remains split outside the manifest plan boundary (src/lib/actions/sandbox/policy-channel.ts:856): WeChat account/baseUrl/userId state remains special-cased in channel lifecycle and rebuild glue instead of being owned wholly by manifest hooks or plan state. During a source-of-truth migration, this split state makes deferred rebuild and resume behavior harder to reason about and can drift from the plan-backed channel model.
    • Recommendation: Move WeChat metadata hydration and persistence behind manifest hooks or the plan-owned state boundary, or document it as a temporary compatibility path with the invalid state handled, source boundary, regression test, and removal condition.
    • Evidence: hydrateAddChannelEnvFromSession() reads session.wechatConfig into process.env and persistWechatConfigFromEnv() writes WeChat metadata back to the onboard session. Rebuild also continues WeChat seed/hydration behavior, while the WeChat manifest already declares state/rebuild hydration concepts.
  • Channel lifecycle can report success even if plan persistence fails (src/lib/actions/sandbox/policy-channel.ts:1070): Several channel lifecycle paths call MessagingHostStateApplier.applyPlanToRegistry() for durable plan state but do not check the boolean result. If the sandbox entry is missing, the plan sandboxName mismatches, or the registry update fails, the command can still report the channel as enabled/removed and queue a rebuild after provider or policy side effects have already run, leaving gateway/policy state out of sync with durable registry state.
    • Recommendation: Check the return value anywhere applyPlanToRegistry() is used in add/remove lifecycle paths. If persistence fails, stop before reporting success or rebuilding; where earlier side effects already happened, rollback or print explicit recovery guidance.
    • Evidence: The in-sandbox QR add branch and token-backed add branch call MessagingHostStateApplier.applyPlanToRegistry(sandboxName, plan) without checking the result. persistManifestChannelRemovePlan() similarly ignores applyPlanToRegistry() failure.

Workflow run details

This is an automated advisory review. A human maintainer must make the final merge decision.

sandl99 and others added 3 commits June 8, 2026 16:59
…ort, function or class'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@sandl99 sandl99 added refactor PR restructures code without intended behavior change area: messaging Messaging channels, bridges, manifests, or channel lifecycle labels Jun 8, 2026
Base automatically changed from feat/phase-4a-messaging-conflict-plan-driven to main June 8, 2026 15:04
…ession-registry

# Conflicts:
#	ci/test-file-size-budget.json
#	src/lib/actions/sandbox/policy-channel-conflict.test.ts
#	src/lib/actions/sandbox/policy-channel.ts
#	src/lib/inventory/index.ts
#	src/lib/messaging/applier/conflict-detection-legacy.test.ts
#	src/lib/messaging/compiler/workflow-planner.test.ts
#	src/lib/messaging/compiler/workflow-planner.ts
#	src/lib/onboard.ts
#	src/lib/state/registry.ts
#	src/lib/status-command-deps.ts
#	test/channels-add-preset.test.ts
#	test/channels-remove-full-teardown.test.ts
#	test/cli/status-health.test.ts
#	test/onboard-messaging.test.ts
#	test/rebuild-credential-preflight.test.ts
@copy-pr-bot

copy-pr-bot Bot commented Jun 8, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@NVIDIA NVIDIA deleted a comment from github-actions Bot Jun 9, 2026
Signed-off-by: San Dang <san1201.bkhn@gmail.com>
@NVIDIA NVIDIA deleted a comment from github-actions Bot Jun 9, 2026
sandl99 and others added 3 commits June 9, 2026 10:25
…ession-registry

Signed-off-by: San Dang <san1201.bkhn@gmail.com>

# Conflicts:
#	ci/test-file-size-budget.json
#	test/cli/status-health.test.ts
Signed-off-by: San Dang <san1201.bkhn@gmail.com>
@sandl99 sandl99 marked this pull request as ready for review June 9, 2026 03:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/lib/onboard/machine/handlers/sandbox.ts (1)

265-302: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve existing session plan when no fresher plan source exists.

At Line 265, messagingPlan is initialized to null, then persisted at Lines 299-301 and Line 345. If both env and registry return no plan, this overwrites an existing session.messagingPlan with null during resume, dropping persisted channel/disabled state for later resume/rebuild paths.

Suggested fix
-    let messagingPlan: SandboxMessagingPlan | null = null;
+    let messagingPlan: SandboxMessagingPlan | null = session?.messagingPlan ?? null;
...
     session = deps.updateSession((current) => {
-      current.messagingPlan = messagingPlan;
+      current.messagingPlan = messagingPlan ?? current.messagingPlan ?? null;
       return current;
     });

Also applies to: 337-346

🤖 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/lib/onboard/machine/handlers/sandbox.ts` around lines 265 - 302, The code
currently initializes messagingPlan to null and then writes it into session via
deps.updateSession, which can erase an existing session.messagingPlan when
neither env nor registry provide a fresher plan; change the logic so
messagingPlan is initialized from the current session (e.g., messagingPlan =
session?.messagingPlan) and only overwrite it when
deps.readMessagingPlanFromEnv() or
deps.getRegistrySandboxMessagingPlan(sandboxName) return a non-null plan; ensure
the final deps.updateSession((current) => { current.messagingPlan =
messagingPlan ?? current.messagingPlan; return current; }) preserves the prior
session.messagingPlan when no new plan is found.
src/lib/messaging/applier/host-state-applier.test.ts (1)

53-75: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen this test to actually prove legacy-field cleanup on existing entries.

Line 54 seeds an entry without legacy keys, so Lines 74-75 pass even if cleanup regresses. Seed legacy fields first, then assert all three are removed (messagingChannels, messagingChannelConfig, disabledChannels).

Suggested test adjustment
   it("stores only the new messaging state on an existing sandbox entry", () => {
     registryMock.__setSandbox("demo", {
       name: "demo",
+      messagingChannels: ["telegram"],
+      messagingChannelConfig: { telegram: { requireMention: true } },
+      disabledChannels: ["telegram"],
     });
     const plan = makePlan(["telegram"]);
@@
     expect(registryMock.__getSandbox("demo")).not.toHaveProperty("messagingChannels");
+    expect(registryMock.__getSandbox("demo")).not.toHaveProperty("messagingChannelConfig");
     expect(registryMock.__getSandbox("demo")).not.toHaveProperty("disabledChannels");
   });
🤖 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/lib/messaging/applier/host-state-applier.test.ts` around lines 53 - 75,
The test currently seeds a sandbox without legacy keys so it can't prove
cleanup; update the setup for registryMock.__setSandbox("demo", ...) to include
legacy fields messagingChannels, messagingChannelConfig, and disabledChannels
with sample values, then call
MessagingHostStateApplier.applyPlanToRegistry("demo", plan) and assert it
returns true, assert registry.updateSandbox was called with the new messaging
object (schemaVersion and plan), and assert registryMock.__getSandbox("demo") no
longer has properties messagingChannels, messagingChannelConfig, or
disabledChannels while still matching the new messaging structure.
src/lib/onboard.ts (1)

368-381: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use configured plan channels here, not only active ones.

getRecordedMessagingChannelsForResumeFromState() is the resume seed for channel selection, so passing getActiveChannelsFromPlan(...) drops plan entries that are configured but currently disabled. On rebuild/resume that can turn a stopped channel into “not selected”, which loses the state channels start needs to reattach cached credentials.

Suggested fix
-const { getActiveChannelsFromPlan } = messagingPlanSession;
+const { getChannelsFromPlan } = messagingPlanSession;
...
-    sessionMessagingChannels: getActiveChannelsFromPlan(session?.messagingPlan),
+    sessionMessagingChannels: getChannelsFromPlan(session?.messagingPlan),

As per coding guidelines, src/lib/onboard.ts changes affect channel disable/enable lifecycle across rebuild with cached credentials, and the PR objective here is to keep configured, active, and disabled plan state distinct.

Also applies to: 5198-5206

🤖 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/lib/onboard.ts` around lines 368 - 381, The code is using
getActiveChannelsFromPlan(...) as the seed for
getRecordedMessagingChannelsForResumeFromState(), which drops plan entries that
are configured but currently disabled; change the call so the resume seed uses
the plan's configured channels instead of only active ones — i.e., pass the
configured channel list (obtainable from the plan/config: use
messagingPlanSession or
collectMessagingBuildConfig/getStoredMessagingChannelConfig outputs) into
getRecordedMessagingChannelsForResumeFromState rather than
getActiveChannelsFromPlan, so disabled-but-configured channels are preserved for
resume and credential reattachment.

Source: Coding guidelines

🤖 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 `@src/lib/policy/index.ts`:
- Around line 779-782: The console.log that reports "Widening sandbox egress —
adding: ..." is currently executed right after const endpoints =
getPresetEndpoints(presetContent) and can fire even when the subsequent
precondition checks abort (lines around the apply-preconditions block). Move the
log statement so it runs only after the precondition pass/abort logic (i.e.,
after the block that currently decides to abort on failure), ensuring you still
reference the same endpoints variable returned by getPresetEndpoints; keep the
log position immediately before the code that actually applies the egress
changes so operators only see the "adding" message when changes will be applied.

In `@test/e2e/docs/parity-inventory.generated.json`:
- Line 7989: The generated JSON fixture parity-inventory.generated.json is
missing SPDX metadata; update the generation routine that produces this file so
the output includes SPDX fields (e.g., a top-level "$comment" object or entries)
containing SPDX-FileCopyrightText and SPDX-License-Identifier like other
fixtures; modify the generator (the code that writes
parity-inventory.generated.json) to prepend or embed the SPDX "$comment"
metadata before/alongside the root object (preserving keys such as "text") so
the produced JSON matches the SPDX format used by other test fixtures.

---

Outside diff comments:
In `@src/lib/messaging/applier/host-state-applier.test.ts`:
- Around line 53-75: The test currently seeds a sandbox without legacy keys so
it can't prove cleanup; update the setup for registryMock.__setSandbox("demo",
...) to include legacy fields messagingChannels, messagingChannelConfig, and
disabledChannels with sample values, then call
MessagingHostStateApplier.applyPlanToRegistry("demo", plan) and assert it
returns true, assert registry.updateSandbox was called with the new messaging
object (schemaVersion and plan), and assert registryMock.__getSandbox("demo") no
longer has properties messagingChannels, messagingChannelConfig, or
disabledChannels while still matching the new messaging structure.

In `@src/lib/onboard.ts`:
- Around line 368-381: The code is using getActiveChannelsFromPlan(...) as the
seed for getRecordedMessagingChannelsForResumeFromState(), which drops plan
entries that are configured but currently disabled; change the call so the
resume seed uses the plan's configured channels instead of only active ones —
i.e., pass the configured channel list (obtainable from the plan/config: use
messagingPlanSession or
collectMessagingBuildConfig/getStoredMessagingChannelConfig outputs) into
getRecordedMessagingChannelsForResumeFromState rather than
getActiveChannelsFromPlan, so disabled-but-configured channels are preserved for
resume and credential reattachment.

In `@src/lib/onboard/machine/handlers/sandbox.ts`:
- Around line 265-302: The code currently initializes messagingPlan to null and
then writes it into session via deps.updateSession, which can erase an existing
session.messagingPlan when neither env nor registry provide a fresher plan;
change the logic so messagingPlan is initialized from the current session (e.g.,
messagingPlan = session?.messagingPlan) and only overwrite it when
deps.readMessagingPlanFromEnv() or
deps.getRegistrySandboxMessagingPlan(sandboxName) return a non-null plan; ensure
the final deps.updateSession((current) => { current.messagingPlan =
messagingPlan ?? current.messagingPlan; return current; }) preserves the prior
session.messagingPlan when no new plan is found.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 68f84691-37d0-4121-9c42-9e97991dc3f2

📥 Commits

Reviewing files that changed from the base of the PR and between d9db199 and a30c537.

📒 Files selected for processing (65)
  • ci/test-file-size-budget.json
  • src/lib/actions/inference-route-api.test.ts
  • src/lib/actions/inference-set.test.ts
  • src/lib/actions/sandbox/channel-status.test.ts
  • src/lib/actions/sandbox/channel-status.ts
  • src/lib/actions/sandbox/doctor.ts
  • src/lib/actions/sandbox/policy-channel-conflict.test.ts
  • src/lib/actions/sandbox/policy-channel.ts
  • src/lib/actions/sandbox/rebuild.ts
  • src/lib/inventory/index.test.ts
  • src/lib/inventory/index.ts
  • src/lib/messaging/applier/conflict-detection-legacy.test.ts
  • src/lib/messaging/applier/conflict-detection/backfill.ts
  • src/lib/messaging/applier/conflict-detection/entries.ts
  • src/lib/messaging/applier/conflict-detection/index.ts
  • src/lib/messaging/applier/conflict-detection/probe.ts
  • src/lib/messaging/applier/conflict-detection/types.ts
  • src/lib/messaging/applier/host-state-applier.test.ts
  • src/lib/messaging/compiler/workflow-planner.test.ts
  • src/lib/messaging/compiler/workflow-planner.ts
  • src/lib/onboard.ts
  • src/lib/onboard/channel-state.test.ts
  • src/lib/onboard/channel-state.ts
  • src/lib/onboard/machine/events.ts
  • src/lib/onboard/machine/handlers/policies.test.ts
  • src/lib/onboard/machine/handlers/policies.ts
  • src/lib/onboard/machine/handlers/sandbox-messaging-plan.test.ts
  • src/lib/onboard/machine/handlers/sandbox.test.ts
  • src/lib/onboard/machine/handlers/sandbox.ts
  • src/lib/onboard/messaging-config.ts
  • src/lib/onboard/messaging-credentials.ts
  • src/lib/onboard/messaging-plan-session.test.ts
  • src/lib/onboard/messaging-plan-session.ts
  • src/lib/onboard/messaging-reuse.test.ts
  • src/lib/onboard/messaging-reuse.ts
  • src/lib/onboard/session-updates.ts
  • src/lib/policy/index.ts
  • src/lib/sandbox/whatsapp-diagnostics.ts
  • src/lib/state/onboard-session.test.ts
  • src/lib/state/onboard-session.ts
  • src/lib/state/registry-messaging.ts
  • src/lib/state/registry.ts
  • src/lib/status-command-deps.ts
  • test/channels-add-preset.test.ts
  • test/channels-remove-full-teardown.test.ts
  • test/cli/connect-recovery.test.ts
  • test/cli/doctor-gateway-token.test.ts
  • test/cli/list-share-live-inference.test.ts
  • test/cli/logs.test.ts
  • test/e2e/docs/parity-inventory.generated.json
  • test/e2e/test-channels-add-remove.sh
  • test/e2e/test-channels-stop-start.sh
  • test/e2e/test-messaging-providers.sh
  • test/e2e/test-rebuild-hermes.sh
  • test/helpers/messaging-plan-fixtures.ts
  • test/helpers/sandbox-handler-fixtures.ts
  • test/nemoclaw-start.test.ts
  • test/onboard-messaging.test.ts
  • test/onboard.test.ts
  • test/policies.test.ts
  • test/rebuild-credential-preflight.test.ts
  • test/rebuild-shields-auto-unlock.test.ts
  • test/registry.test.ts
  • test/repro-2201.test.ts
  • test/secret-redaction.test.ts
💤 Files with no reviewable changes (9)
  • src/lib/messaging/applier/conflict-detection/probe.ts
  • src/lib/messaging/applier/conflict-detection-legacy.test.ts
  • src/lib/messaging/applier/conflict-detection/backfill.ts
  • src/lib/onboard/session-updates.ts
  • test/onboard.test.ts
  • src/lib/messaging/applier/conflict-detection/types.ts
  • src/lib/messaging/compiler/workflow-planner.test.ts
  • src/lib/messaging/applier/conflict-detection/index.ts
  • src/lib/state/onboard-session.ts

Comment thread src/lib/policy/index.ts Outdated
Comment thread test/e2e/docs/parity-inventory.generated.json
sandl99 added 3 commits June 9, 2026 11:09
Signed-off-by: San Dang <san1201.bkhn@gmail.com>
…stry' into feat/messaging-plan-session-registry
Signed-off-by: San Dang <san1201.bkhn@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/policies.test.ts (1)

735-735: 💤 Low value

Consider simplifying the mock spy implementation.

The mockImplementation throws a sentinel error when the disclosure string is logged, but the test at Line 747-Line 752 still verifies the log by checking mock.calls. The throw is caught by the inner try-catch at Line 742-Line 745, making it redundant. Either:

  • Remove the throw and keep the no-op mock with the mock.calls assertion, or
  • Catch and verify the sentinel error explicitly instead of checking mock.calls.

As written, the throw stops applyPreset mid-execution but the test doesn't rely on that behavior.

♻️ Proposed simplification
-      const logSpy = vi.spyOn(console, "log").mockImplementation((message) => { if (typeof message === "string" && message.includes("Widening sandbox egress")) throw new Error("__disclosure_logged__"); });
+      const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});

The test assertion at Line 747-Line 752 already verifies the log was called with the correct message.

🤖 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 `@test/policies.test.ts` at line 735, The console.log spy implementation
currently throws a sentinel error which short-circuits applyPreset; change the
mock to a no-op recorder instead (i.e., vi.spyOn(console,
"log").mockImplementation(() => {}) so it doesn't throw) and keep the assertion
that inspects logSpy.mock.calls to verify the "Widening sandbox egress" message;
reference the existing logSpy and the applyPreset call so you update the spy
used by that test.
🤖 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.

Nitpick comments:
In `@test/policies.test.ts`:
- Line 735: The console.log spy implementation currently throws a sentinel error
which short-circuits applyPreset; change the mock to a no-op recorder instead
(i.e., vi.spyOn(console, "log").mockImplementation(() => {}) so it doesn't
throw) and keep the assertion that inspects logSpy.mock.calls to verify the
"Widening sandbox egress" message; reference the existing logSpy and the
applyPreset call so you update the spy used by that test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 149e3ac7-9734-4d70-8aec-193ce65eed01

📥 Commits

Reviewing files that changed from the base of the PR and between a30c537 and 6a79070.

📒 Files selected for processing (3)
  • src/lib/policy/index.ts
  • test/e2e/docs/parity-inventory.generated.json
  • test/policies.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/e2e/docs/parity-inventory.generated.json

@NVIDIA NVIDIA deleted a comment from github-actions Bot Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Selective E2E Results — ❌ Some jobs failed

Run: 27183660048
Target ref: feat/messaging-plan-session-registry
Requested jobs: all (no filter)
Summary: 46 passed, 18 failed, 2 skipped

Job Result
agent-turn-latency-e2e ✅ success
bedrock-runtime-compatible-anthropic-e2e ✅ success
brave-search-e2e ✅ success
channels-add-remove-e2e ❌ failure
channels-stop-start-e2e ✅ success
cloud-e2e ❌ failure
cloud-inference-e2e ❌ failure
cloud-onboard-e2e ❌ failure
common-egress-agent-e2e ❌ failure
concurrent-gateway-ports-e2e ❌ failure
credential-migration-e2e ❌ failure
credential-sanitization-e2e ✅ success
device-auth-health-e2e ✅ success
diagnostics-e2e ✅ success
docs-validation-e2e ✅ success
double-onboard-e2e ❌ failure
gpu-double-onboard-e2e ⏭️ skipped
gpu-e2e ⏭️ skipped
hermes-anthropic-inference-switch-e2e ✅ success
hermes-dashboard-e2e ✅ success
hermes-discord-e2e ❌ failure
hermes-e2e ✅ success
hermes-inference-switch-e2e ❌ failure
hermes-onboard-security-posture-e2e ❌ failure
hermes-root-entrypoint-smoke-e2e ✅ success
hermes-secret-boundary-e2e ✅ success
hermes-slack-e2e ✅ success
inference-routing-e2e ✅ success
issue-2478-crash-loop-recovery-e2e ✅ success
issue-3600-gpu-proof-optional-e2e ✅ success
issue-4434-tui-unreachable-inference-e2e ✅ success
issue-4462-gateway-pinned-approval-characterization-e2e ❌ failure
issue-4462-scope-upgrade-approval-e2e ✅ success
kimi-inference-compat-e2e ✅ success
launchable-smoke-e2e ✅ success
messaging-compatible-endpoint-e2e ✅ success
messaging-providers-e2e ✅ success
network-policy-e2e ❌ failure
onboard-negative-paths-e2e ❌ failure
onboard-repair-e2e ❌ failure
onboard-resume-e2e ✅ success
openclaw-anthropic-inference-switch-e2e ✅ success
openclaw-discord-pairing-e2e ❌ failure
openclaw-inference-switch-e2e ✅ success
openclaw-onboard-security-posture-e2e ❌ failure
openclaw-skill-cli-e2e ✅ success
openclaw-slack-pairing-e2e ✅ success
openclaw-tui-chat-correlation-e2e ✅ success
openshell-gateway-upgrade-e2e ✅ success
overlayfs-autofix-e2e ✅ success
rebuild-hermes-e2e ✅ success
rebuild-hermes-stale-base-e2e ✅ success
rebuild-openclaw-e2e ✅ success
runtime-overrides-e2e ✅ success
sandbox-operations-e2e ✅ success
sandbox-survival-e2e ✅ success
sessions-agents-cli-e2e ✅ success
shields-config-e2e ✅ success
skill-agent-e2e ❌ failure
snapshot-commands-e2e ✅ success
state-backup-restore-e2e ✅ success
telegram-injection-e2e ✅ success
token-rotation-e2e ✅ success
tunnel-lifecycle-e2e ✅ success
upgrade-stale-sandbox-e2e ✅ success
vm-driver-privileged-exec-routing-e2e ✅ success

Failed jobs: channels-add-remove-e2e, cloud-e2e, cloud-inference-e2e, cloud-onboard-e2e, common-egress-agent-e2e, concurrent-gateway-ports-e2e, credential-migration-e2e, double-onboard-e2e, hermes-discord-e2e, hermes-inference-switch-e2e, hermes-onboard-security-posture-e2e, issue-4462-gateway-pinned-approval-characterization-e2e, network-policy-e2e, onboard-negative-paths-e2e, onboard-repair-e2e, openclaw-discord-pairing-e2e, openclaw-onboard-security-posture-e2e, skill-agent-e2e. Check run artifacts for logs.

1 similar comment
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Selective E2E Results — ❌ Some jobs failed

Run: 27183660048
Target ref: feat/messaging-plan-session-registry
Requested jobs: all (no filter)
Summary: 46 passed, 18 failed, 2 skipped

Job Result
agent-turn-latency-e2e ✅ success
bedrock-runtime-compatible-anthropic-e2e ✅ success
brave-search-e2e ✅ success
channels-add-remove-e2e ❌ failure
channels-stop-start-e2e ✅ success
cloud-e2e ❌ failure
cloud-inference-e2e ❌ failure
cloud-onboard-e2e ❌ failure
common-egress-agent-e2e ❌ failure
concurrent-gateway-ports-e2e ❌ failure
credential-migration-e2e ❌ failure
credential-sanitization-e2e ✅ success
device-auth-health-e2e ✅ success
diagnostics-e2e ✅ success
docs-validation-e2e ✅ success
double-onboard-e2e ❌ failure
gpu-double-onboard-e2e ⏭️ skipped
gpu-e2e ⏭️ skipped
hermes-anthropic-inference-switch-e2e ✅ success
hermes-dashboard-e2e ✅ success
hermes-discord-e2e ❌ failure
hermes-e2e ✅ success
hermes-inference-switch-e2e ❌ failure
hermes-onboard-security-posture-e2e ❌ failure
hermes-root-entrypoint-smoke-e2e ✅ success
hermes-secret-boundary-e2e ✅ success
hermes-slack-e2e ✅ success
inference-routing-e2e ✅ success
issue-2478-crash-loop-recovery-e2e ✅ success
issue-3600-gpu-proof-optional-e2e ✅ success
issue-4434-tui-unreachable-inference-e2e ✅ success
issue-4462-gateway-pinned-approval-characterization-e2e ❌ failure
issue-4462-scope-upgrade-approval-e2e ✅ success
kimi-inference-compat-e2e ✅ success
launchable-smoke-e2e ✅ success
messaging-compatible-endpoint-e2e ✅ success
messaging-providers-e2e ✅ success
network-policy-e2e ❌ failure
onboard-negative-paths-e2e ❌ failure
onboard-repair-e2e ❌ failure
onboard-resume-e2e ✅ success
openclaw-anthropic-inference-switch-e2e ✅ success
openclaw-discord-pairing-e2e ❌ failure
openclaw-inference-switch-e2e ✅ success
openclaw-onboard-security-posture-e2e ❌ failure
openclaw-skill-cli-e2e ✅ success
openclaw-slack-pairing-e2e ✅ success
openclaw-tui-chat-correlation-e2e ✅ success
openshell-gateway-upgrade-e2e ✅ success
overlayfs-autofix-e2e ✅ success
rebuild-hermes-e2e ✅ success
rebuild-hermes-stale-base-e2e ✅ success
rebuild-openclaw-e2e ✅ success
runtime-overrides-e2e ✅ success
sandbox-operations-e2e ✅ success
sandbox-survival-e2e ✅ success
sessions-agents-cli-e2e ✅ success
shields-config-e2e ✅ success
skill-agent-e2e ❌ failure
snapshot-commands-e2e ✅ success
state-backup-restore-e2e ✅ success
telegram-injection-e2e ✅ success
token-rotation-e2e ✅ success
tunnel-lifecycle-e2e ✅ success
upgrade-stale-sandbox-e2e ✅ success
vm-driver-privileged-exec-routing-e2e ✅ success

Failed jobs: channels-add-remove-e2e, cloud-e2e, cloud-inference-e2e, cloud-onboard-e2e, common-egress-agent-e2e, concurrent-gateway-ports-e2e, credential-migration-e2e, double-onboard-e2e, hermes-discord-e2e, hermes-inference-switch-e2e, hermes-onboard-security-posture-e2e, issue-4462-gateway-pinned-approval-characterization-e2e, network-policy-e2e, onboard-negative-paths-e2e, onboard-repair-e2e, openclaw-discord-pairing-e2e, openclaw-onboard-security-posture-e2e, skill-agent-e2e. Check run artifacts for logs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: messaging Messaging channels, bridges, manifests, or channel lifecycle refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 4c: migrate session and registry messaging state to plans

2 participants