Skip to content

refactor(policy): migrate messaging policy to plan-derived presets#4909

Draft
sandl99 wants to merge 11 commits into
mainfrom
feat/messaging-policy-migrate-to-plan-presets
Draft

refactor(policy): migrate messaging policy to plan-derived presets#4909
sandl99 wants to merge 11 commits into
mainfrom
feat/messaging-policy-migrate-to-plan-presets

Conversation

@sandl99
Copy link
Copy Markdown
Contributor

@sandl99 sandl99 commented Jun 7, 2026

Summary

Migrates all messaging policy preset computation away from raw channel string lists and onto messagingPlan.networkPolicy.presets. The compiled plan is now the single source of truth for which network policy presets active messaging channels require, including disabled-channel exclusion and stale-preset cleanup detection.

Related Issue

Closes #4394

Changes

  • messaging-plan-session.ts — add getPolicyPresetsFromPlan helper that reads plan.networkPolicy.presets directly
  • messaging/applier/policy-presets.ts (new) — pruneDisabledMessagingPolicyPresets (retained for backup-manifest paths without a plan) and ALL_MESSAGING_POLICY_PRESET_NAMES; moves and slims down onboard/messaging-policy-presets.ts (deleted)
  • policy-selection.ts — replace enabledChannels/disabledChannels with messagingPolicyPresets: string[] across SetupPolicySelectionOptions, SetupPresetSuggestionOptions, mergeRequiredSetupPolicyPresets, and preparePolicyPresetResumeSelection; stale-preset detection uses ALL_MESSAGING_POLICY_PRESET_NAMES diff against plan presets
  • handlers/policies.ts — replace selectedMessagingChannels, mergePolicyMessagingChannels, and getActiveSandbox deps with a single messagingPolicyPresets: string[]; remove recordedMessagingChannels from result type
  • initial-policy.ts — add messagingPolicyPresets option; remove requiredMessagingChannelPolicyPresets call
  • onboard.ts — wire getPolicyPresetsFromPlan(session.messagingPlan) into handlePoliciesState and prepareInitialSandboxCreatePolicy
  • rebuild.ts — update import to new messaging/applier/policy-presets location
  • Tests updated; messaging-policy-presets.test.ts deleted, messaging/applier/policy-presets.test.ts added

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

  • 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

Release Notes

  • Refactor

    • Restructured messaging policy preset configuration across onboarding and policy selection workflows to source from messaging plans rather than derived channel states.
    • Reorganized messaging policy preset helper functions into a dedicated module with improved channel name normalization and validation.
  • Tests

    • Added comprehensive test coverage for messaging policy preset handling, including disabled channel pruning and preset validation.
    • Updated existing tests to align with reorganized preset sourcing and plan-based configuration.

sandl99 and others added 6 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>
Move messaging policy application into src/lib/messaging/applier/ and
replace channel-list-based preset computation with plan.networkPolicy.presets
throughout the policy selection, resume, and sandbox create flows.

- Add getPolicyPresetsFromPlan helper to messaging-plan-session
- Move pruneDisabledMessagingPolicyPresets to messaging/applier/policy-presets;
  delete merged/computed functions superseded by the compiled plan
- Replace enabledChannels/disabledChannels with messagingPolicyPresets in
  SetupPolicySelectionOptions, SetupPresetSuggestionOptions, and
  preparePolicyPresetResumeSelection; stale-preset detection now uses
  ALL_MESSAGING_POLICY_PRESET_NAMES diff against plan presets
- Remove selectedMessagingChannels, mergePolicyMessagingChannels, and
  getActiveSandbox from handlePoliciesState; handler now accepts a single
  messagingPolicyPresets: string[] derived from the session plan
- Wire getPolicyPresetsFromPlan into onboard.ts handlePoliciesState call and
  prepareInitialSandboxCreatePolicy sandbox create policy
- Update rebuild.ts import to new applier location

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@sandl99 sandl99 self-assigned this Jun 7, 2026
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Jun 7, 2026

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e3eaf906-8c48-45cf-92d1-5bdf6ceee8f1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/messaging-policy-migrate-to-plan-presets

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

PR Review Advisor

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

Review findings

🛠️ Needs attention

  • Channel lifecycle policy mutations still use channel-name preset branches (src/lib/actions/sandbox/policy-channel.ts:1195): Issue Phase 3: migrate messaging network policy to manifests #4394 explicitly requires policy additions/removals to go through manifest/planned policy actions without channel-name policy branches. This PR makes the moved applier helper manifest-aware, but the channel lifecycle implementation still applies and removes policy by using the channel name as the preset name. That can drift from manifest policy aliases or future non-channel preset names, and it leaves the PR short of the linked issue's lifecycle source-of-truth acceptance.
    • Recommendation: Route channels add/remove/start/stop policy changes through compiled manifest plans and MessagingSetupApplier policy application/removal, or narrow the PR/issue closure so this remaining lifecycle migration is not claimed. Add coverage for a manifest where channelId and presetName differ.
    • Evidence: Issue Phase 3: migrate messaging network policy to manifests #4394 scope says, "Apply policy additions/removals through `MessagingSetupApplier` without channel-name policy branches." Nearby production lifecycle code still has `applyChannelPresetIfAvailable(sandboxName, channelName)` calling `policies.applyPreset(sandboxName, channelName)` at `src/lib/actions/sandbox/policy-channel.ts:1195-1208`, and `removeChannelPresetIfPresent()` removes by `channelName` starting at `src/lib/actions/sandbox/policy-channel.ts:1319`.
  • Acceptance-critical lifecycle and rebuild paths are not proven with compiled plan source shapes (test/rebuild-policy-presets.test.ts:148): The changed tests cover helper behavior and some direct policy-selection arrays, but issue Phase 3: migrate messaging network policy to manifests #4394 requires add/remove/start/stop compatibility, session/registry policy sync, rebuild carryforward, unsupported-agent filtering, and dry-run behavior. The highest-risk paths are runtime-style flows where compiled or persisted plans feed onboarding, channel lifecycle, and rebuild; those are still not directly exercised enough to prove disabled-channel egress cannot be reintroduced.
    • Recommendation: Add behavior tests that compile or persist messaging plans, then exercise initial sandbox policy merge, non-interactive resume policy sync, channel stop/start registry/session sync, and rebuild carryforward for disabled Slack and non-Slack channels. Include a case where `networkPolicy.presets` still contains a disabled channel but enabled `networkPolicy.entries` exclude it.
    • Evidence: Issue Phase 3: migrate messaging network policy to manifests #4394 acceptance says, "Tests cover preset resolution, add/remove sync, rebuild carryforward, unsupported-agent filtering, and dry-run behavior." Current rebuild tests such as `test/rebuild-policy-presets.test.ts:148` call `pruneDisabledMessagingPolicyPresets()` directly, and policy selection tests often pass `messagingPolicyPresets: []` directly rather than driving the real compiled/registry plan through the lifecycle/rebuild call chain.
  • Policy-selection monolith grew while adding policy source-of-truth logic (src/lib/onboard/policy-selection.ts:1): `policy-selection.ts` is already a large, security-sensitive policy orchestration hotspot. This PR adds active-vs-disabled messaging policy preservation and stale cleanup logic while growing the file, which makes the policy source-of-truth boundary harder to audit and contradicts the repository's large-file growth guardrail.
    • Recommendation: Extract plan-derived active messaging preset/channel derivation, stale messaging preset detection, and fallback cleanup into a small shared module or further expand the new `messaging-policy-state.ts` boundary, then offset the growth in `policy-selection.ts`.
    • Evidence: Deterministic monolith analysis reports `src/lib/onboard/policy-selection.ts` grew from 501 to 522 lines, delta +21, with blocker severity. It also reports `src/lib/onboard/machine/handlers/policies.test.ts` grew from 342 to 404 lines, delta +62.

🔎 Worth checking

  • Source-of-truth review needed: Backup-manifest disabled messaging preset cleanup: 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: `src/lib/messaging/applier/policy-presets.ts:76-82` documents the fallback as for backup manifests where no compiled plan is available.
  • Source-of-truth review needed: Null or missing messaging plan compatibility: 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: `resolveMessagingPolicyState()` falls back to selected/recorded/active channels when `getPolicyPresetsFromPlan()` returns null.
  • Source-of-truth review needed: Env-staged messaging plan parsing: 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: `src/lib/onboard/messaging-channel-setup.ts:257-258` still delegates to `MessagingSetupApplier.readPlanFromEnv()`, whose assertion checks only broad top-level shape.
  • Env-staged messaging plans remain shallow-validated before driving network policy (src/lib/onboard/messaging-plan-session.ts:8): The new persisted-plan parser validates policy-sensitive fields more deeply, but env-staged plans still decode through the applier's shallow top-level assertion before being persisted and used for create-time/resume policy decisions. A malformed-but-shallow-valid env plan can crash policy setup or influence sandbox network policy selection incorrectly.
    • Recommendation: Use `parseSandboxMessagingPlan()` for env-staged plans as well, or move the stricter validation into `MessagingSetupApplier.decodePlan/readPlanFromEnv`. Validate channel enabled/disabled flags and `networkPolicy.entries` before any policy selection or sandbox create path consumes the plan.
    • Evidence: `parseSandboxMessagingPlan()` now validates `channels[].channelId`, `disabledChannels`, `networkPolicy.presets`, and `networkPolicy.entries` in `src/lib/onboard/messaging-plan-session.ts:8-34`, but `src/lib/onboard/messaging-channel-setup.ts:257-258` still returns `MessagingSetupApplier.readPlanFromEnv()`. `MessagingSetupApplier.assertSandboxMessagingPlan()` only checks top-level arrays/objects, while `src/lib/onboard.ts:3353-3364` and `src/lib/onboard/machine/handlers/sandbox.ts:283-302` use env/registry plans for policy-sensitive state.
  • Compatibility fallbacks do not define their removal boundary (src/lib/messaging/applier/policy-presets.ts:76): The backup-manifest pruning and legacy no-plan compatibility paths now have better manifest-aware behavior and regression tests, but they still do not state when the localized workaround can be removed or what schema/version boundary makes compiled plans mandatory. Without that boundary, fallback policy behavior can become a second long-term source of truth.
    • Recommendation: Document the source boundary and removal condition for backup manifests without compiled plans and sessions without `messagingPlan`. Prefer a versioned session/registry migration path that makes the invalid state impossible, or explicitly mark these fallbacks as permanent compatibility APIs.
    • Evidence: `pruneDisabledMessagingPolicyPresets()` is documented as used for "backup manifests where no compiled plan is available" in `src/lib/messaging/applier/policy-presets.ts:76-82`, and `resolveMessagingPolicyState()` preserves legacy `messagingChannels` when `messagingPlan` is null in `src/lib/onboard/messaging-policy-state.ts:55-77`. Tests cover the fallback behaviors, but no removal/version condition is present.

🌱 Nice ideas

  • None.
Consider writing more tests for
  • **Runtime validation** — prepareInitialSandboxCreatePolicy with active Slack channels and no messagingPlan still merges the slack preset into the boot policy. The changed surfaces decide sandbox network policy during onboarding, channel lifecycle operations, and rebuild. Helper/unit tests improved, but runtime-style validation is still needed for compiled plan, persisted plan, env-staged plan, and legacy no-plan source shapes.
  • **Runtime validation** — onboard createSandbox with a legacy no-plan Slack session passes legacy active channels and boots with slack policy. The changed surfaces decide sandbox network policy during onboarding, channel lifecycle operations, and rebuild. Helper/unit tests improved, but runtime-style validation is still needed for compiled plan, persisted plan, env-staged plan, and legacy no-plan source shapes.
  • **Runtime validation** — non-interactive resume with registry messagingPlan disabling Telegram removes telegram policy while preserving npm and pypi. The changed surfaces decide sandbox network policy during onboarding, channel lifecycle operations, and rebuild. Helper/unit tests improved, but runtime-style validation is still needed for compiled plan, persisted plan, env-staged plan, and legacy no-plan source shapes.
  • **Runtime validation** — rebuild with registry compiled plan disabling Telegram does not include telegram in sandbox create policy even when backupManifest.policyPresets contains telegram. The changed surfaces decide sandbox network policy during onboarding, channel lifecycle operations, and rebuild. Helper/unit tests improved, but runtime-style validation is still needed for compiled plan, persisted plan, env-staged plan, and legacy no-plan source shapes.
  • **Runtime validation** — channels stop telegram updates registry.messagingPlan and session.policyPresets so subsequent onboard --resume removes telegram policy. The changed surfaces decide sandbox network policy during onboarding, channel lifecycle operations, and rebuild. Helper/unit tests improved, but runtime-style validation is still needed for compiled plan, persisted plan, env-staged plan, and legacy no-plan source shapes.
  • **Acceptance clause:** Have the planner produce network policy actions for onboard, add, remove, start, stop, and rebuild. — add test evidence or identify existing coverage. Planner output is consumed by new plan helpers and rebuild staging, but the changed tests do not exercise all lifecycle source shapes through compiled/persisted plans.
  • **Acceptance clause:** Apply policy additions/removals through `MessagingSetupApplier` without channel-name policy branches. — add test evidence or identify existing coverage. Production lifecycle code still applies/removes policy by channel name in `src/lib/actions/sandbox/policy-channel.ts:1195` and `src/lib/actions/sandbox/policy-channel.ts:1319`, rather than through manifest plan policy application/removal.
  • **Acceptance clause:** Preserve initial sandbox-create policy merging and existing `policyPresets` session/registry sync behavior. — add test evidence or identify existing coverage. `createSandbox()` now passes plan-derived enabled channels and `messagingPolicyPresets`, while falling back to legacy active channels when no plan exists. However, direct create-time regression coverage for legacy active Slack/Telegram with `messagingPlan=null` is still missing.
Since last review details

Current findings:

  • Source-of-truth review needed: Backup-manifest disabled messaging preset cleanup: 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: `src/lib/messaging/applier/policy-presets.ts:76-82` documents the fallback as for backup manifests where no compiled plan is available.
  • Source-of-truth review needed: Null or missing messaging plan compatibility: 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: `resolveMessagingPolicyState()` falls back to selected/recorded/active channels when `getPolicyPresetsFromPlan()` returns null.
  • Source-of-truth review needed: Env-staged messaging plan parsing: 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: `src/lib/onboard/messaging-channel-setup.ts:257-258` still delegates to `MessagingSetupApplier.readPlanFromEnv()`, whose assertion checks only broad top-level shape.
  • Channel lifecycle policy mutations still use channel-name preset branches (src/lib/actions/sandbox/policy-channel.ts:1195): Issue Phase 3: migrate messaging network policy to manifests #4394 explicitly requires policy additions/removals to go through manifest/planned policy actions without channel-name policy branches. This PR makes the moved applier helper manifest-aware, but the channel lifecycle implementation still applies and removes policy by using the channel name as the preset name. That can drift from manifest policy aliases or future non-channel preset names, and it leaves the PR short of the linked issue's lifecycle source-of-truth acceptance.
    • Recommendation: Route channels add/remove/start/stop policy changes through compiled manifest plans and MessagingSetupApplier policy application/removal, or narrow the PR/issue closure so this remaining lifecycle migration is not claimed. Add coverage for a manifest where channelId and presetName differ.
    • Evidence: Issue Phase 3: migrate messaging network policy to manifests #4394 scope says, "Apply policy additions/removals through `MessagingSetupApplier` without channel-name policy branches." Nearby production lifecycle code still has `applyChannelPresetIfAvailable(sandboxName, channelName)` calling `policies.applyPreset(sandboxName, channelName)` at `src/lib/actions/sandbox/policy-channel.ts:1195-1208`, and `removeChannelPresetIfPresent()` removes by `channelName` starting at `src/lib/actions/sandbox/policy-channel.ts:1319`.
  • Env-staged messaging plans remain shallow-validated before driving network policy (src/lib/onboard/messaging-plan-session.ts:8): The new persisted-plan parser validates policy-sensitive fields more deeply, but env-staged plans still decode through the applier's shallow top-level assertion before being persisted and used for create-time/resume policy decisions. A malformed-but-shallow-valid env plan can crash policy setup or influence sandbox network policy selection incorrectly.
    • Recommendation: Use `parseSandboxMessagingPlan()` for env-staged plans as well, or move the stricter validation into `MessagingSetupApplier.decodePlan/readPlanFromEnv`. Validate channel enabled/disabled flags and `networkPolicy.entries` before any policy selection or sandbox create path consumes the plan.
    • Evidence: `parseSandboxMessagingPlan()` now validates `channels[].channelId`, `disabledChannels`, `networkPolicy.presets`, and `networkPolicy.entries` in `src/lib/onboard/messaging-plan-session.ts:8-34`, but `src/lib/onboard/messaging-channel-setup.ts:257-258` still returns `MessagingSetupApplier.readPlanFromEnv()`. `MessagingSetupApplier.assertSandboxMessagingPlan()` only checks top-level arrays/objects, while `src/lib/onboard.ts:3353-3364` and `src/lib/onboard/machine/handlers/sandbox.ts:283-302` use env/registry plans for policy-sensitive state.
  • Acceptance-critical lifecycle and rebuild paths are not proven with compiled plan source shapes (test/rebuild-policy-presets.test.ts:148): The changed tests cover helper behavior and some direct policy-selection arrays, but issue Phase 3: migrate messaging network policy to manifests #4394 requires add/remove/start/stop compatibility, session/registry policy sync, rebuild carryforward, unsupported-agent filtering, and dry-run behavior. The highest-risk paths are runtime-style flows where compiled or persisted plans feed onboarding, channel lifecycle, and rebuild; those are still not directly exercised enough to prove disabled-channel egress cannot be reintroduced.
    • Recommendation: Add behavior tests that compile or persist messaging plans, then exercise initial sandbox policy merge, non-interactive resume policy sync, channel stop/start registry/session sync, and rebuild carryforward for disabled Slack and non-Slack channels. Include a case where `networkPolicy.presets` still contains a disabled channel but enabled `networkPolicy.entries` exclude it.
    • Evidence: Issue Phase 3: migrate messaging network policy to manifests #4394 acceptance says, "Tests cover preset resolution, add/remove sync, rebuild carryforward, unsupported-agent filtering, and dry-run behavior." Current rebuild tests such as `test/rebuild-policy-presets.test.ts:148` call `pruneDisabledMessagingPolicyPresets()` directly, and policy selection tests often pass `messagingPolicyPresets: []` directly rather than driving the real compiled/registry plan through the lifecycle/rebuild call chain.
  • Policy-selection monolith grew while adding policy source-of-truth logic (src/lib/onboard/policy-selection.ts:1): `policy-selection.ts` is already a large, security-sensitive policy orchestration hotspot. This PR adds active-vs-disabled messaging policy preservation and stale cleanup logic while growing the file, which makes the policy source-of-truth boundary harder to audit and contradicts the repository's large-file growth guardrail.
    • Recommendation: Extract plan-derived active messaging preset/channel derivation, stale messaging preset detection, and fallback cleanup into a small shared module or further expand the new `messaging-policy-state.ts` boundary, then offset the growth in `policy-selection.ts`.
    • Evidence: Deterministic monolith analysis reports `src/lib/onboard/policy-selection.ts` grew from 501 to 522 lines, delta +21, with blocker severity. It also reports `src/lib/onboard/machine/handlers/policies.test.ts` grew from 342 to 404 lines, delta +62.
  • Compatibility fallbacks do not define their removal boundary (src/lib/messaging/applier/policy-presets.ts:76): The backup-manifest pruning and legacy no-plan compatibility paths now have better manifest-aware behavior and regression tests, but they still do not state when the localized workaround can be removed or what schema/version boundary makes compiled plans mandatory. Without that boundary, fallback policy behavior can become a second long-term source of truth.
    • Recommendation: Document the source boundary and removal condition for backup manifests without compiled plans and sessions without `messagingPlan`. Prefer a versioned session/registry migration path that makes the invalid state impossible, or explicitly mark these fallbacks as permanent compatibility APIs.
    • Evidence: `pruneDisabledMessagingPolicyPresets()` is documented as used for "backup manifests where no compiled plan is available" in `src/lib/messaging/applier/policy-presets.ts:76-82`, and `resolveMessagingPolicyState()` preserves legacy `messagingChannels` when `messagingPlan` is null in `src/lib/onboard/messaging-policy-state.ts:55-77`. Tests cover the fallback behaviors, but no removal/version condition is present.

Workflow run details

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

E2E Advisor Recommendation

Required E2E: messaging-providers-e2e, channels-stop-start-e2e, rebuild-openclaw-e2e, network-policy-e2e, hermes-slack-e2e, onboard-resume-e2e
Optional E2E: channels-add-remove-e2e, hermes-discord-e2e, cloud-onboard-e2e

Dispatch hint: messaging-providers-e2e,channels-stop-start-e2e,rebuild-openclaw-e2e,network-policy-e2e,hermes-slack-e2e,onboard-resume-e2e

Workflow run

Full advisor summary

E2E Recommendation Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required E2E

  • messaging-providers-e2e (high): Required because the PR changes messaging policy preset derivation and applier exports that affect Telegram, Discord, Slack, and WhatsApp provider/placeholder/policy setup. This job validates the end-to-end messaging credential provider and L7 proxy chain with real sandbox policy application.
  • channels-stop-start-e2e (very-high): Required because the PR changes disabled-channel pruning and active plan policy preservation. This job specifically exercises stop/start disablement across rebuild for OpenClaw and Hermes over telegram, discord, wechat, slack, and whatsapp.
  • rebuild-openclaw-e2e (high): Required because src/lib/actions/sandbox/rebuild.ts and rebuild policy-preset preservation/pruning behavior changed. This validates the OpenClaw sandbox rebuild lifecycle against the modified create-time and preserved policy preset paths.
  • network-policy-e2e (medium): Required because initial sandbox network policy generation and policy preset merge/filter logic changed. This verifies that sandbox egress policy boundaries remain enforced after the new plan-derived preset handling.
  • hermes-slack-e2e (high): Required for Hermes messaging coverage because the PR changes per-agent policy key derivation from manifests and Hermes policy filtering. Hermes Slack validates a Hermes messaging channel with provider credential rewriting and policy setup.
  • onboard-resume-e2e (medium): Required because policy handling now resolves messaging policy state from session messaging plans, recorded channels, active sandbox state, and disabled channels. Resume coverage is needed to catch stale policy/session regressions in real onboarding.

Optional E2E

  • channels-add-remove-e2e (high): Useful adjacent coverage for add/remove plus rebuild flows with Telegram credentials, but channels-stop-start-e2e is the stronger merge-blocking signal for disabled policy pruning.
  • hermes-discord-e2e (high): Additional Hermes messaging confidence with a different channel manifest and provider path. Optional if hermes-slack-e2e is run as required.
  • cloud-onboard-e2e (medium): Useful broad onboarding confidence for custom policy preset selection without messaging, but less targeted than the required messaging/policy/rebuild jobs.

New E2E recommendations

  • hermes-telegram-messaging-policy (high): This PR specifically changes the Telegram manifest to use a Hermes agent policy alias, but the existing typed scenarios and nightly jobs include OpenClaw Telegram plus Hermes Discord/Slack, not Hermes Telegram. A real Hermes Telegram onboard would validate that the new alias applies the correct sandbox policy and that Telegram env/config render correctly for Hermes.
    • Suggested test: Add a hermes-telegram-e2e job or typed scenario using --agent hermes with TELEGRAM_BOT_TOKEN that verifies Telegram provider attachment, Hermes config rendering, and the telegram policy key is applied.
  • messaging-plan-policy-pruning (medium): Existing jobs cover channel lifecycle broadly, but there is no focused E2E assertion that a compiled messaging plan with disabled channels removes stale messaging-only policy presets while preserving non-messaging presets during resume/rebuild.
    • Suggested test: Add a focused E2E that onboards with multiple messaging channels, disables one, rebuilds/resumes, and asserts the disabled channel policy key is absent while unrelated presets such as npm/pypi remain applied.

Dispatch hint

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

E2E Scenario Advisor Recommendation

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

Dispatch required scenario E2E:

  • 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-hermes-slack
  • gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-resume

Workflow run

Full scenario advisor summary

E2E Scenario Advisor

Base: origin/main
Head: HEAD
Confidence: high

Required scenario E2E

  • ubuntu-repo-cloud-openclaw-telegram: Primary coverage for the changed Telegram manifest, messaging policy preset derivation, onboarding plan/session handling, sandbox create-time messaging policy application, and common messaging provider/config secret-safety checks on the OpenClaw path.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-telegram
  • ubuntu-repo-cloud-hermes-slack: Exercises the Hermes messaging onboarding path affected by manifest-owned policy preset/key derivation and inactive Hermes messaging policy filtering; this is the smallest routed Hermes messaging scenario available.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-hermes-slack
  • ubuntu-repo-cloud-openclaw-resume: Covers the resume/interrupted-onboarding path touched by the sandbox and policies state-handler changes, including session/registry messaging state reuse before finalization.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-resume

Optional scenario E2E

  • ubuntu-repo-cloud-openclaw-token-rotation: Adjacent messaging lifecycle coverage for provider/token state changes; useful if maintainers want extra assurance around stale messaging policy/provider cleanup but not the smallest required target.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-openclaw-token-rotation
  • ubuntu-repo-cloud-hermes-discord: Adjacent Hermes messaging scenario on a second channel to broaden confidence in manifest-derived policy key handling across Hermes channel manifests.
    • Dispatch: gh workflow run e2e-scenarios.yaml --ref <pr-head-ref> --field scenarios=ubuntu-repo-cloud-hermes-discord

Relevant changed files

  • src/lib/actions/sandbox/rebuild.ts
  • src/lib/messaging/applier/index.ts
  • src/lib/messaging/applier/policy-presets.ts
  • src/lib/messaging/channels/telegram/manifest.ts
  • src/lib/onboard.ts
  • src/lib/onboard/initial-policy.ts
  • src/lib/onboard/machine/handlers/policies.ts
  • src/lib/onboard/machine/handlers/sandbox.ts
  • src/lib/onboard/messaging-plan-session.ts
  • src/lib/onboard/messaging-policy-presets.ts
  • src/lib/onboard/messaging-policy-state.ts
  • src/lib/onboard/policy-selection.ts

Base automatically changed from feat/persist-messaging-plan-in-session to main June 7, 2026 12:32
sandl99 and others added 3 commits June 7, 2026 21:27
…policy

Three CI failures in the messaging policy migration:

1. onboard-messaging.test.ts — prepareInitialSandboxCreatePolicy received empty
   messagingPolicyPresets because createSandbox was called with messagingPlan=null
   when invoked directly (bypassing the handler). Fix: thread messagingPlan through
   the createSandbox dep signature; fall back to readMessagingPlanFromEnv() when
   caller passes null (direct call path).

2. onboard-policy-suggestions.test.ts — computeSetupPresetSuggestions stopped
   adding channel names (telegram, discord) as preset suggestions because the old
   enabledChannels loop was removed. Fix: add messagingChannelIds option that
   adds channel IDs as suggested preset names, mirroring the old behavior.
   Also add inactive-preset filtering: when messagingPolicyPresets is explicitly
   provided (even as []), remove messaging presets not in the active set so
   disabled-channel presets don't appear via open-tier defaults.

3. onboard-preset-diff.test.ts — tests used enabledChannels/disabledChannels which
   are no longer on SetupPolicySelectionOptions. Updated to messagingPolicyPresets
   (for required/merge) and messagingPolicyPresets: [] (for disabled-channel pruning).

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

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The source-shape budget blocks net growth in src/lib/onboard.ts. Move the
messagingChannelIds derivation (session plan channel ID extraction) into
handlers/policies.ts where latestSession is already loaded, removing the two
extra lines from the onboard.ts setupPoliciesWithSelection wrapper.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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/policy-selection.ts (3)

294-296: ⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Similar null-handling issue: all messaging presets filtered out when plan is null.

Line 294 constructs appliedToPreserve by filtering messaging presets that are in planMessagingPresets, which defaults to an empty set when options.messagingPolicyPresets is null (line 286).

The filter condition !ALL_MESSAGING_POLICY_PRESET_NAMES.has(name) || planMessagingPresets.has(name) keeps presets that are either:

  • Not a messaging preset, OR
  • Present in the plan's messaging presets

When planMessagingPresets is empty (from null), all messaging presets are filtered out.

Impact:
For legacy sandboxes or scenarios where the plan is unavailable, previously-applied messaging presets would be removed from the preserved set, even though there's no evidence they're stale.

Recommendation:
Guard the messaging-preset filter:

-  const appliedToPreserve = appliedPolicyPresetsForSupport.filter(
-    (name) => !ALL_MESSAGING_POLICY_PRESET_NAMES.has(name) || planMessagingPresets.has(name),
-  );
+  const appliedToPreserve = options.messagingPolicyPresets !== null
+    ? appliedPolicyPresetsForSupport.filter(
+        (name) => {
+          const planMessagingPresets = new Set(options.messagingPolicyPresets);
+          return !ALL_MESSAGING_POLICY_PRESET_NAMES.has(name) || planMessagingPresets.has(name);
+        },
+      )
+    : appliedPolicyPresetsForSupport;

Or reuse the planMessagingPresets check from the earlier suggestion and only filter when options.messagingPolicyPresets !== null.

🤖 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/policy-selection.ts` around lines 294 - 296, The filter that
builds appliedToPreserve removes all messaging presets when
options.messagingPolicyPresets is null because planMessagingPresets defaults to
an empty set; update the logic in the appliedToPreserve computation (which reads
appliedPolicyPresetsForSupport and uses ALL_MESSAGING_POLICY_PRESET_NAMES and
planMessagingPresets) to only require membership in planMessagingPresets when
options.messagingPolicyPresets !== null (i.e., if options.messagingPolicyPresets
is null treat messaging presets as allowed to preserve), so that messaging
presets are not dropped for legacy/plan-unavailable cases.

377-382: ⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Third instance of null-handling inconsistency in preset preservation logic.

Lines 377–382 compute appliedForPreservation using messagingPolicyPresets ?? [] (line 377), which filters out all messaging presets when the plan is null.

This is the same pattern as the issue flagged in preparePolicyPresetResumeSelection (lines 294–296). For consistency and correctness, the guard should be applied here as well:

-  const planMessagingPresets = new Set(messagingPolicyPresets ?? []);
   const appliedForPreservation = applied.filter(
     (name) =>
       !isStaleBuiltinBrave(name) &&
-      (!ALL_MESSAGING_POLICY_PRESET_NAMES.has(name) || planMessagingPresets.has(name)),
+      (!ALL_MESSAGING_POLICY_PRESET_NAMES.has(name) || 
+        (messagingPolicyPresets !== null && new Set(messagingPolicyPresets).has(name))),
   );

Or refactor to compute planMessagingPresets conditionally once and reuse.

🤖 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/policy-selection.ts` around lines 377 - 382, The preservation
filter currently treats a null messagingPolicyPresets as an empty array (using
messagingPolicyPresets ?? []) which incorrectly drops all plan messaging
presets; update the logic around planMessagingPresets and appliedForPreservation
so you only consult planMessagingPresets when the plan exists (e.g., compute
planMessagingPresets conditionally or check messagingPolicyPresets != null
before using planMessagingPresets.has), and reuse this guarded value rather than
always defaulting to []—adjust the code around the planMessagingPresets,
messagingPolicyPresets, and appliedForPreservation symbols (same approach used
in preparePolicyPresetResumeSelection) to ensure consistent null-handling.

286-290: ⚠️ Potential issue | 🟡 Minor

Fix misleading null-handling concern for disabledMessagingPolicyPresetApplied: at src/lib/onboard/machine/handlers/policies.ts, PoliciesStateOptions.messagingPolicyPresets is typed as string[] (not null/undefined) and that exact value is passed through to preparePolicyPresetResumeSelection as messagingPolicyPresets, so options.messagingPolicyPresets ?? [] in src/lib/onboard/policy-selection.ts will effectively behave like “plan presets provided”, not as a “null plan” case. The proposed guard against options.messagingPolicyPresets !== null is therefore unnecessary for the actual resume-selection call path.

🤖 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/policy-selection.ts` around lines 286 - 290, The
null-coalescing fallback is misleading because
PoliciesStateOptions.messagingPolicyPresets is always a string[] in the
resume-selection call path; update the logic to treat
options.messagingPolicyPresets as the authoritative plan presets. Replace the
creation of planMessagingPresets to use options.messagingPolicyPresets directly
(no ?? []), and keep the disabledMessagingPolicyPresetApplied computation using
appliedPolicyPresetsForSupport, ALL_MESSAGING_POLICY_PRESET_NAMES, and
planMessagingPresets unchanged otherwise; also remove any related guard or
comment that suggests messagingPolicyPresets can be null in
preparePolicyPresetResumeSelection.
🤖 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.

Outside diff comments:
In `@src/lib/onboard/policy-selection.ts`:
- Around line 294-296: The filter that builds appliedToPreserve removes all
messaging presets when options.messagingPolicyPresets is null because
planMessagingPresets defaults to an empty set; update the logic in the
appliedToPreserve computation (which reads appliedPolicyPresetsForSupport and
uses ALL_MESSAGING_POLICY_PRESET_NAMES and planMessagingPresets) to only require
membership in planMessagingPresets when options.messagingPolicyPresets !== null
(i.e., if options.messagingPolicyPresets is null treat messaging presets as
allowed to preserve), so that messaging presets are not dropped for
legacy/plan-unavailable cases.
- Around line 377-382: The preservation filter currently treats a null
messagingPolicyPresets as an empty array (using messagingPolicyPresets ?? [])
which incorrectly drops all plan messaging presets; update the logic around
planMessagingPresets and appliedForPreservation so you only consult
planMessagingPresets when the plan exists (e.g., compute planMessagingPresets
conditionally or check messagingPolicyPresets != null before using
planMessagingPresets.has), and reuse this guarded value rather than always
defaulting to []—adjust the code around the planMessagingPresets,
messagingPolicyPresets, and appliedForPreservation symbols (same approach used
in preparePolicyPresetResumeSelection) to ensure consistent null-handling.
- Around line 286-290: The null-coalescing fallback is misleading because
PoliciesStateOptions.messagingPolicyPresets is always a string[] in the
resume-selection call path; update the logic to treat
options.messagingPolicyPresets as the authoritative plan presets. Replace the
creation of planMessagingPresets to use options.messagingPolicyPresets directly
(no ?? []), and keep the disabledMessagingPolicyPresetApplied computation using
appliedPolicyPresetsForSupport, ALL_MESSAGING_POLICY_PRESET_NAMES, and
planMessagingPresets unchanged otherwise; also remove any related guard or
comment that suggests messagingPolicyPresets can be null in
preparePolicyPresetResumeSelection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 834e28b6-e6f0-4f89-a777-81a073eaf644

📥 Commits

Reviewing files that changed from the base of the PR and between fed526c and e5702c7.

📒 Files selected for processing (8)
  • src/lib/onboard.ts
  • src/lib/onboard/machine/handlers/policies.ts
  • src/lib/onboard/machine/handlers/sandbox.test.ts
  • src/lib/onboard/machine/handlers/sandbox.ts
  • src/lib/onboard/messaging-plan-session.ts
  • src/lib/onboard/policy-selection.ts
  • test/onboard-policy-suggestions.test.ts
  • test/onboard-preset-diff.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/onboard/messaging-plan-session.ts
  • src/lib/onboard.ts
  • src/lib/onboard/machine/handlers/policies.ts

sandl99 and others added 2 commits June 8, 2026 00:18
…s only

Disabled messaging channels could still drive sandbox network policy
because getPolicyPresetsFromPlan spread raw networkPolicy.presets (which
retains disabled-channel entries after compilation) and handlePoliciesState
passed all plan channels as messagingChannelIds regardless of enabled state.

- getPolicyPresetsFromPlan now mirrors applyPolicyAtOpenShell: derives
  presets from filterEnabledPlanEntries(plan, plan.networkPolicy.entries)
- Add getEnabledChannelIdsFromPlan helper; use it in handlePoliciesState
  so disabled same-name channels (e.g. Telegram) are not suggested
- Expand REQUIRED_POLICY_PRESETS_BY_MESSAGING_CHANNEL to cover all five
  messaging channels (discord, telegram, wechat, whatsapp) so
  ALL_MESSAGING_POLICY_PRESET_NAMES enables stale detection for non-Slack
  stopped channels and pruneDisabledMessagingPolicyPresets works in the
  rebuild backup-manifest path for all channels
- Extract hasDisabledMessagingPreset and filterActiveMessagingPresets
  into policy-presets.ts; replace inline occurrences in policy-selection.ts
  to reduce duplication
- Tighten parseSandboxMessagingPlan to validate element types in
  channels[], disabledChannels[], networkPolicy.presets[], and
  networkPolicy.entries[] before accepting a persisted plan
- Add messaging-plan-session.test.ts covering disabled-channel preset
  exclusion, enabled channel ID derivation, and parse validation
- Update rebuild-policy-presets tests to reflect correct behavior for
  all messaging channel presets (not just Slack)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@wscurran wscurran added area: messaging Messaging channels, bridges, manifests, or channel lifecycle area: policy Network policy, egress rules, presets, or sandbox policy refactor PR restructures code without intended behavior change labels Jun 8, 2026
@wscurran
Copy link
Copy Markdown
Contributor

wscurran commented Jun 8, 2026


Related open issues:

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 area: policy Network policy, egress rules, presets, or sandbox policy refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3: migrate messaging network policy to manifests

2 participants