Skip to content

feat(integrations): support chatRoomFilter and chat rule types - #433

Merged
AndyTWF merged 11 commits into
mainfrom
integrations-chat-room-filter
Aug 14, 2026
Merged

feat(integrations): support chatRoomFilter and chat rule types#433
AndyTWF merged 11 commits into
mainfrom
integrations-chat-room-filter

Conversation

@AndyTWF

@AndyTWFAndyTWF commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Ably Chat integration rules populate chatRoomFilter (top-level on the rule, alongside source) rather than source.channelFilter. ably integrations get/list previously had no way to display this, so chat-room-sourced rules looked like they had no filter configured. integrations get/list now show a "Chat Room Filter" line when set, and include chatRoomFilter in --json output.
  • integrations create/update gained a --chat-room-filter flag and chat.message as a --source-type option — but on their own these only covered the classic Reactor-style rule types (http, amqp, etc.), which turned out not to be the actual rule types Ably Chat rules use.
  • Investigating against the real Control API showed that chat-sourced rules (moderation vendors, before-publish webhooks, dashboards) use a structurally different schema: invocationMode + beforePublishConfig instead of requestMode, and a source object that rejects channelFilter entirely. integrations create had no way to create any of these rule types at all. This PR adds full support for all seven: http/before-publish, hive/text-model-only, hive/dashboard, bodyguard/text-moderation, tisane/text-moderation, azure/text-moderation, and aws/lambda/before-publish. Each gets its own typed target flags (--target-api-key, --target-endpoint, --target-function-name, --threshold key=value, etc.), and invocationMode/beforePublishConfig are set automatically per rule type (only hive/dashboard runs AFTER_PUBLISH; everything else is BEFORE_PUBLISH with a configurable retry/failure policy).
  • Fixed two bugs uncovered while building this: chatRoomFilter and source.channelFilter were being sent unconditionally (even as empty strings), which the Control API rejects outright for chat.message-sourced and before-publish rules — now both are only included when actually set. A modelUrl: null default was also rejected by the schema (it only accepts a string or omission) — now omitted when not provided.
  • Rule/RuleData interfaces in control-api.ts updated to match: invocationMode, beforePublishConfig added, requestMode/source.channelFilter made optional.
  • integrations get/list also surface invocationMode and no longer print a blank "Request Mode" line for rule types that don't have one.
  • Corrected channelFilter/chatRoomFilter example and test values to valid regex syntax (e.g. chat:.*, room:.*) instead of glob-style chat:* — both fields are regexps evaluated by the Control API, not globs.

Test plan

  • pnpm prepare (build + manifest)
  • pnpm exec eslint . — 0 errors
  • pnpm test:unit — full suite passes (2553 tests)
  • pnpm test:tty — passes
  • Unit tests added/updated for integrations create/get/list/update covering all seven chat rule types, required-flag validation per type, beforePublishConfig defaults/overrides, and the hive/dashboardAFTER_PUBLISH/no-beforePublishConfig exception
  • e2e suite (test/e2e/integrations/integrations-e2e.test.ts) extended with a full create/get/delete lifecycle for every chat rule type, plus a fix to the existing chat-room-sourced test which used the wrong (http) rule type. Actually run against the real Control API (with E2E_ABLY_ACCESS_TOKEN/E2E_ABLY_API_KEY) this time — all 10 tests pass, including app create/teardown
  • Manually verified all seven rule types create successfully via node bin/run.js integrations create ... against a real app, then cleaned up the test rules

Notes from live verification

  • azure/text-moderation's target.endpoint is validated via a live DNS lookup at rule-creation time (422 on ENOTFOUND), so the e2e test uses a real resolvable hostname rather than a placeholder.
  • Found (not fixed here, since it's unrelated to this feature and likely affects other ID-taking commands too): rule IDs can start with - (e.g. -MYkHg), which oclif's arg parser misreads as an unknown flag rather than a positional argument for integrations get/delete. Worked around it in the new e2e tests with a -- separator; flagging as a follow-up since it'd affect real users with an unlucky rule ID.

DX-1546

Manual testing

Run against a local build of this branch (pnpm prepare then node bin/run.js ...) — the globally installed ably CLI will be an older published version without this support.

  1. Create a before-publish webhook sourced from a chat room:
    node bin/run.js integrations create --rule-type "http/before-publish" --source-type "chat.message" --chat-room-filter "room:.*" --target-url "https://example.com/webhook"
    
    Confirm the output includes Chat Room Filter: room:.* and Invocation Mode: BEFORE_PUBLISH.
  2. Create a moderation rule, e.g. tisane:
    node bin/run.js integrations create --rule-type "tisane/text-moderation" --source-type "chat.message" --target-api-key "key" --threshold "profanity=1" --threshold "allegation=1" --default-language "*"
    
  3. Get the rule and confirm the new fields are shown:
    node bin/run.js integrations get <ruleId>
    node bin/run.js integrations get <ruleId> --json # confirm "chatRoomFilter"/"invocationMode"/"beforePublishConfig" are present
    
  4. List rules and confirm chat rules show their filter/invocation mode, and pre-existing channel.message rules show neither:
    node bin/run.js integrations list
    node bin/run.js integrations list --json
    
  5. Update a rule's chat room filter and re-verify via get:
    node bin/run.js integrations update <ruleId> --chat-room-filter "chat:.*"
    node bin/run.js integrations get <ruleId>
    
  6. Delete the rule to clean up:
    node bin/run.js integrations delete <ruleId>
    

@vercel

vercelBot commented Jul 17, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
cli-web-cliReadyReadyPreviewAug 14, 2026 10:41am

Request Review

@umair-ablyumair-ably 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.

just a couple things, nothing major

Comment threadsrc/commands/integrations/update.ts Outdated
}

// Update target if it's an HTTP rule and target-url is provided
if (existingRule.ruleType === "http" && flags["target-url"]) {

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.

does this need updating to work with but http/before-publish? that also takes a target-url which can't be updated with this guard

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good spot, done :)

`${formatLabel("Invocation Mode")} ${integration.invocationMode}`,
);
}

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.

more of nit... we seem to guard and log the chatRoomFilter in get.ts line 69... any reason not to have it here too?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done!

source: { type: "chat.message" },
target: {
apiKey: "hive-key",
modelUrl: null,

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.

should we test whether we're actually sending this or not? this just mocks the response?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done!

@AndyTWF
AndyTWFforce-pushed the integrations-chat-room-filter branch from c2e85ad to 3b29706CompareAugust 11, 2026 16:33
@AndyTWF
AndyTWF marked this pull request as ready for review August 11, 2026 16:33
@claude-code-ably-assistant

Copy link
Copy Markdown

Walkthrough

This PR adds full support for Ably Chat integration rule types in the integrations family of commands. Chat-sourced rules use a structurally different schema from classic Reactor rules (invocationMode + beforePublishConfig instead of requestMode), and previously had no way to be created via the CLI — and even get/list silently omitted their chatRoomFilter field. The PR adds seven new chat rule types to integrations create, surfaces chatRoomFilter and invocationMode across all read commands, and fixes two bugs where empty-string fields were sent unconditionally to the Control API (causing 422s).

Changes

AreaFilesSummary
Commandssrc/commands/integrations/create.tsAdds 7 chat rule types (http/before-publish, hive/text-model-only, hive/dashboard, bodyguard/text-moderation, tisane/text-moderation, azure/text-moderation, aws/lambda/before-publish) via a declarative config table; new flags --chat-room-filter, --target-api-key, --target-endpoint, --target-function-name, --threshold, --default-language, --invocation-mode, --retry-count, --fail-on-error; adds cross-validation that chat rule types require --source-type chat.message
Commandssrc/commands/integrations/get.ts, list.tsSurface chatRoomFilter and invocationMode in both human-readable and --json output; suppress blank "Request Mode" line for rule types that use invocationMode instead
Commandssrc/commands/integrations/update.ts, delete.tsFix the same "Request Mode: undefined" display bug that was already fixed in get/list; update gains --chat-room-filter flag
Servicessrc/services/control-api.tsExtends Rule/RuleData interfaces with invocationMode and beforePublishConfig; makes requestMode and source.channelFilter optional to reflect that chat rules don't include them
Tests — E2Etest/e2e/integrations/integrations-e2e.test.tsFull create/get/delete lifecycle for all 7 chat rule types against the real Control API; fixes existing chat-room-sourced test that used the wrong rule type (http) and would have failed if actually run
Tests — Unittest/unit/commands/integrations/create.test.ts650+ lines of new tests covering all 7 chat rule types, required-flag validation per type, beforePublishConfig defaults/overrides, source-type cross-validation, and hive/dashboard AFTER_PUBLISH exception
Tests — Unittest/unit/commands/integrations/get.test.ts, list.test.ts, update.test.ts, delete.test.tsUpdated to cover new chatRoomFilter/invocationMode display fields and corrected filter values to valid regex syntax (chat:.* instead of chat:*)
Tests — Fixturestest/fixtures/control-api.tsMinor fixture update to support new test scenarios

Review Notes

  • New flags on integrations create — the command now has a fairly large flag surface for chat rule types. Each rule type has a distinct set of required flags; attempting to use the wrong combination gives a clear CLI error rather than a 422 from the API. Worth validating the flag descriptions are clear.
  • parseThresholds bug fixed — previously accepted "key=" / "key= " as a threshold of 0 and silently discarded everything after a second =. Now properly validated.
  • invocationMode auto-set — only hive/dashboard runs AFTER_PUBLISH; all other chat types default to BEFORE_PUBLISH. This logic lives in the new config table in create.ts.
  • Known follow-up (not fixed here) — rule IDs can start with -, which oclif misparses as an unknown flag on integrations get/delete. The e2e tests work around this with --. Flagged as a follow-up since it affects other ID-taking commands too.
  • E2E tests verified against real API — all 10 pass including app create/teardown. azure/text-moderation requires a resolvable hostname for target.endpoint (live DNS lookup at creation time), so the test uses a real hostname rather than a placeholder.
  • No new runtime dependencies introduced.

@claude-code-ably-assistantclaude-code-ably-assistantBot 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

Overall this is solid work — the investigation against the real API is clearly thorough, the data-table approach for chat rule types is a clean design, and the e2e coverage is impressive. A few things worth fixing before merge:


1. --chat-room-filter can silently end up in channel-rule payloads

File:src/commands/integrations/create.ts

The assignment happens unconditionally before the chatRuleConfig branch:

if(flags["chat-room-filter"]){integrationData.chatRoomFilter=flags["chat-room-filter"];// set here}if(chatRuleConfig){// then branched here
...
}else{// channel rules — chatRoomFilter is already on integrationData}

So a user running:

ably integrations create --rule-type http --source-type channel.message \
--chat-room-filter "room:.*" --target-url "https://example.com"

…will get the chatRoomFilter silently included in the API payload. The API returns a 422, but the user sees a confusing upstream error rather than a helpful CLI message.

The existing source-type/rule-type cross-validation doesn't catch this because it checks for chat.message source mismatch — not for --chat-room-filter appearing on a channel rule.

Fix: Add a guard after the cross-type validation:

if(!chatRuleConfig&&flags["chat-room-filter"]){fail(`--chat-room-filter is only valid for chat rule types (e.g. --rule-type "http/before-publish")`);}

2. --failed-action and --too-many-requests-action lack options constraints

File:src/commands/integrations/create.ts (~lines where these flags are defined)

Both flags accept free-form strings, but the Control API only accepts a limited set of values. Without options: [...], a typo like --failed-action REJCT makes a round-trip to the API and returns a 422 instead of being caught at parse time by oclif.

Based on the test cases that verify "IGNORE" and "DROP", the valid values appear to be:

  • --failed-action: ["REJECT", "IGNORE"] (possibly others — check the API schema)
  • --too-many-requests-action: ["RETRY", "DROP"] (possibly others)

If the full set of valid values is known, add options: [...] to both flag definitions.


3. --target-secret-access-key in shell history (informational, not a blocker)

File:src/commands/integrations/create.ts

AWS secret access keys passed as CLI flags end up in ~/.bash_history, ~/.zsh_history, and /proc/<pid>/cmdline during execution. This is a common pattern in CLIs (the aws CLI does this too via --secret-access-key), so it's not a blocker, but it's worth a note in the flag description like: "(Caution: appears in shell history — prefer setting via environment variables for production use)".


4. MockRule.source.channelFilter non-optional in test fixture (cosmetic)

File:test/fixtures/control-api.ts:63

MockRule.source is typed as { channelFilter: string; type: string } (channelFilter non-optional), but chat-sourced rules don't have channelFilter. Tests work around this with source: { channelFilter: "", type: "room.message" }. This doesn't cause any failures, but aligning the fixture with the updated Rule interface (channelFilter?: string) would make the intent clearer.


Issues 1 and 2 are the ones worth addressing before merge — both result in confusing user-facing errors for inputs that the CLI could catch itself. The rest are informational.

@AndyTWF
AndyTWF enabled auto-merge August 14, 2026 10:40
AndyTWFand others added 5 commits August 14, 2026 11:40
Rules created from Ably Chat rooms populate chatRoomFilter (top-level
on the rule, alongside source) instead of source.channelFilter. Surface
it in both human-readable and JSON output so `integrations get`/`list`
don't silently omit the filter for chat-room-sourced rules.
DX-1546
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rules can be sourced from Ably Chat rooms (source-type chat.message),
which requires setting chatRoomFilter rather than the channel-based
source.channelFilter. `integrations create`/`update` had no way to set
it, even though get/list already knew how to display it.
Also corrects the example/test filter values to valid regex syntax
(channelFilter/chatRoomFilter are regexps, not globs) — e.g. "room:.*"
instead of "room:*".
DX-1546
Extends the Control API e2e suite to create/get/list/delete a rule
with source-type chat.message and --chat-room-filter, verifying
chatRoomFilter round-trips through the real API end to end.
…ests
channelFilter is a regexp, not a glob, so "chat:*" is invalid — the
trailing * has nothing to repeat. Switch examples and test fixtures to
"chat:.*" (and similarly for other filter values used in tests).
…sh rule types
Ably Chat rules (hive/text-model-only, hive/dashboard, bodyguard/text-moderation,
tisane/text-moderation, azure/text-moderation, aws/lambda/before-publish, and
http/before-publish) use a structurally different schema from the classic
Reactor rule types: invocationMode + beforePublishConfig instead of
requestMode, and vendor-specific target shapes discovered by inspecting
existing rules and probing the Control API directly. `integrations create`
had no way to create any of these — only the classic channel-sourced rule
types were supported.
Along the way, found and fixed two bugs that blocked this entirely:
chatRoomFilter and source.channelFilter were sent unconditionally (even as
empty strings), which the API rejects outright for chat.message-sourced and
before-publish rules. modelUrl was sent as an explicit null when unset,
which the API also rejects — now omitted instead.
DX-1546
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AndyTWFand others added 6 commits August 14, 2026 11:40
… test
Extends the Control API e2e suite with a full create/get/delete lifecycle
for every chat rule type (http/before-publish, hive/text-model-only,
hive/dashboard, bodyguard/text-moderation, tisane/text-moderation,
azure/text-moderation, aws/lambda/before-publish) against the real API.
Also fixes the existing chat-room-sourced rule test, which used the
"http" rule type — the real API rejects chatRoomFilter/chat.message on
that schema, so this test would have failed had it ever actually run
(it was silently skipped locally for lack of E2E_ABLY_ACCESS_TOKEN).
Running the new tests against the real API surfaced two more things
these tests now account for: the azure/text-moderation target.endpoint
is validated via a live DNS lookup at creation time, so a placeholder
domain 422s; and rule IDs can start with "-", which oclif misparses as
an unknown flag unless a "--" separator precedes the positional arg.
DX-1546
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rule config
update.ts and delete.ts printed "Request Mode: undefined" for chat rule
types (they use invocationMode instead of requestMode) and never showed
invocationMode at all — the same display bug already fixed in create.ts/
get.ts/list.ts was missed in these two commands.
parseThresholds silently accepted "key=" and "key= " as a threshold of 0
instead of rejecting them, and discarded everything after a second "=".
create.ts had no validation that a chat rule type is paired with
--source-type chat.message (or vice versa), so a mismatched combination
only surfaced as an opaque Control API 4xx instead of a clear CLI error.
Replaces the CHAT_RULE_TYPES/AFTER_PUBLISH_CHAT_RULE_TYPES sets plus the
per-rule-type switch statement with a single declarative config table
(invocationMode, required target flags, target builder per rule type),
removes the duplicate local IntegrationData/BeforePublishConfig types in
favour of importing RuleData from control-api.ts, and extracts a shared
buildModerationTarget helper for the three moderation-vendor rule types.
DX-1546
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rules
The rule-type guard in `integrations update` only matched the classic
"http" rule type, so chat-sourced `http/before-publish` rules (which
also target a webhook URL) couldn't have their target URL updated.
integrations get already surfaces chatRoomFilter; the delete
confirmation prompt was missing it, so a chat-sourced rule's filter
was invisible when confirming a destructive action.
--chat-room-filter was applied to integrationData unconditionally,
so combining it with a channel-sourced rule type (e.g. "http") sent
chatRoomFilter in a payload the Control API rejects for that source,
surfacing a confusing 422 instead of a clear CLI error.
The hive/text-model-only and tisane/text-moderation tests only
asserted against the mocked response, not the outgoing request body,
so they didn't actually verify the fix that omits modelUrl when
--target-model-url isn't provided. Also add a positive case covering
--target-model-url being sent when it is provided.
@AndyTWF
AndyTWFforce-pushed the integrations-chat-room-filter branch from d511faf to 0bf37b5CompareAugust 14, 2026 10:40
@AndyTWF
AndyTWF merged commit 3e17173 into mainAug 14, 2026
11 checks passed
@AndyTWF
AndyTWF deleted the integrations-chat-room-filter branch August 14, 2026 10:57
Sign up for freeto 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.

2 participants

@AndyTWF@umair-ably