Skip to content

feat(channels): add two-tier channel visibility policy for resellers - #933

Merged
realcodesiman merged 3 commits into
mainfrom
feat/channel-visibility-policy
Aug 10, 2026
Merged

feat(channels): add two-tier channel visibility policy for resellers#933
realcodesiman merged 3 commits into
mainfrom
feat/channel-visibility-policy

Conversation

@realcodesiman

@realcodesiman realcodesiman commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Lets the platform and individual resellers hide channel types from the create-channel picker and settings accordion, without affecting channels already connected.
  • Adds Tenant.hiddenChannels and tenantService.resolveVisibleChannels, unioning the platform's hidden-channel policy with a reseller's own, root-tenant hides always taking precedence.
  • Consolidates the previously duplicated inline owner-resolution logic (platform vs. workspace-tenant owner) used by both credential lookup and the new visibility checks into apps/builder/src/lib/platform-credential-owner.ts.
  • Introduces packages/utils/src/channel.ts's ChannelCapability registry as the single source of truth for which channels are creatable/manageable/require-a-credential, replacing hardcoded ChannelType[] literals in the create picker and settings accordion that had drifted from each other.

Changes

  • Schema/migration: Tenant.hiddenChannels (jsonb, nullable) — packages/database/drizzle/20260810041827_add_tenant_hidden_channels/
  • Business logic: tenantService.resolveVisibleChannels (packages/business/src/enterprise/tenant/service.ts), resolveChannelCreatable (apps/builder/src/lib/workspace/resolve-channel-creatable.ts)
  • New settings surfaces: /admin/platform-channels, /manage/platform-channels, backed by apps/builder/src/features/platform-channels/ (schema, settings UI, server action)
  • UI: new channels-accordion.tsx and add-channel-button.tsx apply the visibility policy to the settings rows and create-channel entry point
  • Shared registry: packages/utils/src/channel.ts (ChannelCapability)
  • Owner resolution: centralized in apps/builder/src/lib/platform-credential-owner.ts, consumed by channel connect/reconnect/select-account actions across Messenger, Instagram, WhatsApp, Zalo, TikTok, Google Sheets, Facebook Ads/Lead Ads

Test plan

  • pnpm lint
  • pnpm --filter builder check-types
  • pnpm --filter @chatbotx.io/business check-types
  • New/updated Vitest suites green: platform-credential-owner, channels-create-visibility-guard, channels-create-platform-owner, settings-channels-visibility, settings-channels-add-button-gating, webchats-create-visibility-guard, add-channel-button, channel-connect-credential-consistency, tenant-channel-visibility (business package)
  • Manual: platform admin hides a channel type, confirm it disappears from every reseller's create picker; a reseller hides a channel type, confirm it only disappears for their own sub-accounts
  • Manual: confirm already-connected channels of a newly-hidden type keep working (webhooks, send, inbox) — policy is creation-time only

Note: apps/builder/__tests__/channel-route-guards.test.ts has 2 pre-existing failures on main (unrelated targetWorkspace.ownerId mock gap in dashboard tests, verified via git stash against main before this change) — not introduced by this PR.


Follow-up: docs + review fixes (65e00e5)

Post-review refinements on top of the feature commit:

  • docs/tenancy.md — new "Channel-visibility policy" section (was undocumented): UI-gate-not-authorization, platform/reseller tier union, resolveVisibleChannels rules, grandfathering, and tenant-aware owner resolution (host wins over workspaceId).
  • AGENTS.md — invariant #17 create new contact #18 covering the three silent pitfalls (connect actions don't re-check the policy; new gates must pass the tenant-aware owner; new gates must preserve grandfathering). Synced to .devin + copilot mirrors.
  • channels/create/page.tsx — corrected an overclaiming comment: the page gates rendering, not the connect actions, so hiding is a UI hint, not access control.
  • platform-channels-settings.tsx — guarded toggleChannel against an undefined useWatch value before RHF hydrates defaultValues.

Lets the platform and individual resellers hide channel types from the
create-channel picker and settings accordion, without affecting channels
already connected.

- Tenant.hiddenChannels (new column) holds a reseller's own hidden-channel
  list; tenantService.resolveVisibleChannels unions it with the platform's
  policy so root-tenant hides always take precedence.
- New /admin/platform-channels and /manage/platform-channels settings pages
  let the platform and resellers each manage their own list via
  platform-channels feature (schema, settings UI, action).
- resolveChannelCreatable and the new channels-accordion component apply the
  policy to the create picker and the workspace settings channel rows.
- platform-credential-owner.ts centralizes the existing owner-resolution
  logic (resolvePlatformOwnerId, resolveOwnerForWorkspace) used by both the
  credential lookup and the new visibility checks, replacing duplicated
  inline resolution across channel connect/reconnect/select-account actions.
- packages/utils/src/channel.ts introduces a single ChannelCapability
  registry (creatable/manageable/requiresCredential/order) as the source of
  truth for the create picker and settings accordion, replacing separately
  hardcoded ChannelType[] literals that had already drifted from each other.

Covered by new/updated tests for visibility guards, add-channel-button
gating, settings-channels accordion filtering, and credential-owner
resolution consistency across connect/reconnect/select-account flows.
Document the two-tier channel-visibility feature (previously undocumented)
and correct two review findings from the feature commit.

- docs/tenancy.md: new "Channel-visibility policy" section — UI-gate-not-auth,
  platform/reseller tier union, resolveVisibleChannels rules, grandfathering,
  and the tenant-aware owner resolution (host wins over workspaceId)
- AGENTS.md: invariant #18 covering the three silent pitfalls (actions don't
  re-check, must pass tenant-aware owner, must preserve grandfathering); synced
  to .devin and copilot mirrors via sync:agent-instructions
- create/page.tsx: correct overclaiming comment — the page gates rendering, not
  the connect actions, so hiding is a UI hint not access control
- platform-channels-settings.tsx: guard toggleChannel against undefined
  useWatch value before RHF hydrates defaultValues
Drop two debug console.log calls left in the workspace general settings
page's not-found guards.
@realcodesiman
realcodesiman merged commit ef58420 into main Aug 10, 2026
8 checks passed
@realcodesiman
realcodesiman deleted the feat/channel-visibility-policy branch August 10, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant