Skip to content

feat(rules)!: create channel rules as match expressions - #454

Open
SimonWoolf wants to merge 1 commit into
mainfrom
channel-rules-match-expressions
Open

SimonWoolf wants to merge 1 commit into
mainfrom
channel-rules-match-expressions

Conversation

@SimonWoolf

@SimonWoolf SimonWoolf commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Important

Don't release this until ably/website#9882 is deployed. That PR adds mode to the Control API. Before it's deployed, the Control API rejects the mode this now sends, so ably apps rules create would fail outright.

Summary

Channel rules now come in two modes:

  • matcher: the id is a match expression, with the same syntax as a capability resource: chat:*, *:presence, foo:*:baz.
  • namespace: the older kind. The id is one segment matching that channel plus everything beneath it.

A rule's mode is fixed when it's created.

Changes

  • rules create always creates a matcher rule. The help text and examples now use match expressions.
  • rules update sends no mode, so a rule keeps the one it was created with. This is how the dashboard behaves; the Control API rejects an attempt to change mode.
  • create, update and list show each rule's Mode, and include mode in --json output. The same-looking id means different things in each mode, so list would otherwise be ambiguous.

Breaking change

ably apps rules create "chat" used to apply to chat and every channel beneath it. It now applies to chat alone; "chat:*" selects the channels beneath it. Existing rules are unaffected. This needs a line in the release notes (the text is in the commit's BREAKING CHANGE: footer).

Debated whether the CLI should be more like the dashboard (new rules are matcher) or more like the control api (new rules are namespace with explicit mode flag to avoid breaking backcompat). ultimately given it's focused on interactive usecases decided the dashboard was the better analogy, so made it more like the dashboard where new rules are matcher mode. Let me know if you disagree.

Testing

Unit tests cover create sending mode: matcher, update sending no mode to a namespace-mode rule, and list/JSON showing each mode.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cli-web-cli Ready Ready Preview Oct 1, 2026 7:31pm UTC

Request Review

@claude-code-ably-assistant

Copy link
Copy Markdown

Walkthrough

This PR introduces support for the new matcher channel rule mode alongside the pre-existing namespace mode. The rules create command now always creates matcher-mode rules (where the ID is a match expression like chat:*), aligning with dashboard behaviour. The create, list, and update commands all surface mode in their output so callers can distinguish between the two semantically different ID formats.

Changes

Area Files Summary
Commands src/commands/apps/rules/create.ts Always sends mode: "matcher" on create; updates arg description and examples to use match expressions (chat:*, *:presence); includes mode in JSON output
Commands src/commands/apps/rules/list.ts Adds ChannelRuleMode import and mode field to output interface and JSON/human-readable display
Commands src/commands/apps/rules/update.ts Surfaces mode from the API response in output; intentionally omits mode from the PATCH body to preserve an existing rule's mode
Services src/services/control-api.ts Exports new ChannelRuleMode union type ("matcher" | "namespace"), adds mode? to Namespace interface, and mode? to create parameters
Utils src/utils/channel-rule-display.ts Adds a Mode: display line at the top of channel rule detail blocks (shown only when present)
Tests test/fixtures/control-api.ts Adds mode? to MockNamespace fixture interface
Tests test/unit/commands/apps/rules/create.test.ts New test: verifies mode: "matcher" is sent in the POST body and echoed in JSON output
Tests test/unit/commands/apps/rules/list.test.ts New test: verifies both modes are shown in human-readable and --json output
Tests test/unit/commands/apps/rules/update.test.ts New test: verifies mode is absent from the PATCH body, preserving a legacy namespace-mode rule

Review Notes

  • Breaking change — ably apps rules create "chat" previously created a namespace-mode rule matching the chat channel and all sub-channels beneath it. It now creates a matcher-mode rule matching only the literal channel chat. Users must pass "chat:*" to match sub-channels. This is a user-visible behaviour change that needs a release-notes entry (the commit's BREAKING CHANGE: footer has the text).
  • Hard deployment dependency — the PR description flags that this must not be released until ably/website#9882 ships. That PR adds mode to the Control API; without it, ably apps rules create will fail on every call.
  • Mode immutability is tested — update deliberately omits mode from the PATCH request (the Control API rejects attempts to change it). The new update test uses a body matcher (!("mode" in body)) to assert this.
  • No new runtime dependencies.

@claude-code-ably-assistant claude-code-ably-assistant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review summary

The changes are correct and focused. No bugs, no architectural problems, no JSON output contract violations.

What the PR does: Channel rules now have a mode field (matcher or namespace). New rules are always created as matcher mode (match expression syntax), update intentionally omits mode from the PATCH body to preserve whatever mode a rule was created with, and all three commands (create, list, update) now surface mode in both human and JSON output.

Key correctness checks passed:

  • createNamespace passes the full namespaceData object directly to JSON.stringify — mode: "matcher" will be in the POST body ✓
  • updateNamespace's parameter type has no mode field, so it's structurally impossible to accidentally send mode on PATCH ✓
  • formatChannelRuleDetails guards on rule.mode being truthy, so pre-existing rules without a mode field don't emit a blank line ✓
  • this.fail() used correctly throughout (never this.error() directly) ✓
  • JSON output nests data under the rule/rules domain key ✓

One minor observation (not a blocker):

test/fixtures/control-api.ts line 137 defines mode?: "matcher" | "namespace" using inline string literals rather than importing ChannelRuleMode from control-api.ts. This is fine for now, but if a third mode is ever added to the type the fixture will silently fall out of sync. Worth a quick import swap:

import type { ChannelRuleMode } from "../../src/services/control-api.js";
// ...
mode?: ChannelRuleMode;

But that's cosmetic — don't hold the PR for it.

The unit tests cover the three critical invariants: create always sends mode: "matcher", update never sends mode, and list exposes mode in both output formats. Ship it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Update the parent help text and add test coverage for the update command’s JSON mode output.

Review effort: Lite
Findings: 1 Low severity

Open (1)
What changed in this PR

Updates channel rules to support immutable matcher and legacy namespace modes, with new rules using match expressions.

Changes:

  • Creates rules with mode: "matcher".
  • Preserves and displays rule modes in human-readable and JSON output.
  • Adds service types, formatting, fixtures, and unit tests.
File Summary
test/​unit/​commands/​apps/​rules/​update.test.ts Tests preserving namespace mode.
test/​unit/​commands/​apps/​rules/​list.test.ts Tests mode display and JSON output.
test/​unit/​commands/​apps/​rules/​create.test.ts Tests matcher-mode creation.
test/​fixtures/​control-api.ts Extends fixtures with rule modes.
src/​utils/​channel-rule-display.ts Displays rule mode.
src/​services/​control-api.ts Adds channel rule mode types and creation support.
src/​commands/​apps/​rules/​update.ts Displays the persisted rule mode.
src/​commands/​apps/​rules/​list.ts Includes mode in list output.
src/​commands/​apps/​rules/​create.ts Creates matcher-mode rules and updates examples.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/commands/apps/rules/create.ts
Channel rules now come in two modes. A `matcher` rule's id is a match
expression, with the same syntax as a capability resource ("chat:*",
"*:presence", "foo:*:baz"); a `namespace` rule's id is a single segment
that matches that channel plus everything beneath it. New rules should
be matchers - the dashboard only creates matchers - so `rules create`
now always does. `rules update` sends no mode, so a rule keeps the one
it was created with. `create`, `update` and `list` show each rule's
mode, since the same-looking id means different things in each.

BREAKING CHANGE: `ably apps rules create "chat"` used to create a rule
applying to `chat` and every channel beneath it (`chat:room1`, ...). It
now applies to the channel `chat` alone. To select the channels beneath
it, use `ably apps rules create "chat:*"`. Existing rules are unaffected
and keep matching as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

This branch was successfully deployed

1 active deployment
Preview — dbdfb284 Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants