Skip to content

feat: optimize event subscription precheck, links, and consumer guard#1447

Merged
sang-neo03 merged 17 commits into
mainfrom
feat/event-subscription-optimize
Jun 16, 2026
Merged

feat: optimize event subscription precheck, links, and consumer guard#1447
sang-neo03 merged 17 commits into
mainfrom
feat/event-subscription-optimize

Conversation

@sang-neo03

@sang-neo03 sang-neo03 commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

为 event 订阅增加订阅类型标识、回调订阅 precheck、扫码自动开通链接,并支持 per-EventKey 唯一 consumer 限制。本次为基础设施改造,现有事件订阅类 EventKey 行为不变(SubscriptionType 默认 event、SingleConsumer 默认 false)。

Changes

  • Add SubscriptionType (event/callback, empty normalized to event) and SingleConsumer fields to KeyDefinition in internal/event/types.go; normalize/validate in internal/event/registry.go
  • Add appmeta.FetchSubscribedCallbacks (internal/appmeta/app_callbacks.go) calling application/get; route callback-type precheck against subscribed_callbacks in cmd/event/consume.go
  • Replace the three precheck remediation links with a scan-to-enable deep link in cmd/event/console_url.go (ManifestAddons → gzip → base64url, brand host via core.ResolveEndpoints), filling scopes/events/callbacks by identity
  • Reject duplicate consumers for SingleConsumer keys via Hub.TryRegisterExclusive (internal/event/bus/hub.go) at the bus handshake (internal/event/bus/bus.go), surfaced through HelloAck.Rejected (internal/event/protocol/messages.go) as a failed_precondition error in internal/event/consume/consume.go

Test Plan

  • validate passed (build / vet / unit / integration)
  • E2E regression: tests/cli_e2e/event 3/3 passed (lite mode adds no new sandbox E2E)
  • skillave: N/A (no shortcut / skill / meta API changes)
  • acceptance-reviewer passed (lite, 4/4 scenarios + 2 blocked_by_env, no mocks)
  • code security review passed (0 findings); design security review CONDITIONAL_PASS
  • manual verification: lark-cli event consume im.message.recieve_v1 → exit 2 typed validation/invalid_argument; addons URL generation/decoding observed (brand-aware, valid base64url → gzip → JSON matching the addons schema)
  • real E2E against a live app: precheck-pass connects; SingleConsumer second consumer rejected (exit 2); precheck-miss emits the scan link; scan link opens correctly on the open platform (open.feishu.cn/page/launcher?clientID=<appID>&addons=...)

Related Issues

N/A (internal requirement doc: event 事件优化技术方案)

Note: the scan-to-enable landing URL was verified against the open platform — {open-host}/page/launcher?clientID=<appID>&addons=<encoded> (param is camelCase clientID, value is the consuming app's own id).

Summary by CodeRabbit

  • New Features
    • Added support for callback-style subscriptions with stronger preflight validation.
    • Introduced single-consumer (exclusive) subscription behavior, rejecting additional concurrent consumers with a clear reason.
    • Enhanced launcher deep-link generation using an encoded addons manifest for scan/enable flows.
  • Bug Fixes
    • Updated “missing subscription/scope” hints to use the new launcher scan-page format and correct event vs callback selection.
  • Documentation
    • Clarified URL host alternation behavior when expanding brands.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds SubscriptionType and SingleConsumer support to event keys, enforces single-consumer exclusivity at handshake with rejection plumbing, extends console URL generation with launcher addons deep-link encoding for remediation hints, fetches app callback subscriptions via a new API, and updates preflight checks to validate callback subscriptions against the ledger and produce launcher-based hint URLs.

Changes

Event Callback and Exclusive Consumer Feature

Layer / File(s) Summary
Event type system and registration
internal/event/types.go, internal/event/registry.go, internal/event/registry_test.go
New SubscriptionType enum (SubTypeEvent, SubTypeCallback) and constants; KeyDefinition gains optional SubscriptionType and SingleConsumer fields; RegisterKey validates and normalizes subscription type, panics on invalid values.
Protocol message rejection fields
internal/event/protocol/messages.go, internal/event/protocol/messages_test.go
HelloAck message extended with rejected and reject_reason JSON fields; NewHelloAckRejected constructor added to build rejected acks and tested for round-trip encoding/decoding.
Hub exclusive registration support
internal/event/bus/hub.go, internal/event/bus/hub_test.go
Hub.TryRegisterExclusive method added to enforce per-SubscriptionID exclusivity, with configurable cleanup-wait timeout and PID-based rejection reasons; existingPIDForSubscriptionLocked helper scans registered subscribers; three tests cover successful exclusivity, cleanup timeout, and distinct subscriptions.
Bus handshake single-consumer enforcement
internal/event/bus/bus.go, internal/event/bus/handle_hello_test.go
handleHello checks KeyDefinition.SingleConsumer via event.Lookup and uses TryRegisterExclusive for exclusive keys; on conflict, sends HelloAckRejected and closes connection; test added to assert rejection of second consumer with "already running" reason.
Consumer-side rejection handling
internal/event/consume/consume.go, internal/event/consume/reject_test.go
Run inspects doHello handshake response and converts a rejected HelloAck into a SubtypeFailedPrecondition validation error using the server-provided reason and EventKey hint; rejectionError helper and unit tests added.
Console deep-link encoding for launcher addons
cmd/event/console_url.go, cmd/event/console_url_test.go, cmd/event/appmeta_err.go
Adds ManifestAddons struct hierarchy with scopes/events/callbacks sections; encodeAddons implements JSON→gzip→base64url (no padding) pipeline; consoleAddonsURL, consoleLandingURL, addonsHintURL build deep links; routing helpers missingScopeAddons and missingSubscriptionAddons populate manifest sections by identity/subscription type; tests validate encoding round-trips, brand host resolution, identity-specific routing, and empty-array JSON encoding.
Callback subscription fetch API
internal/appmeta/app_callbacks.go, internal/appmeta/app_callbacks_test.go
FetchSubscribedCallbacks calls app-scoped endpoint /open-apis/application/v6/applications/{appID} and returns subscribed callbacks as a non-nil empty slice when callback_info is absent; surfaces fetch and JSON decode errors; tests cover parsing, missing info, and error cases.
Preflight validation with callback support
cmd/event/consume.go, cmd/event/preflight_test.go
runConsume conditionally fetches subscribed callbacks for callback-type EventKeys and stores in preflightCtx.subscribedCallbacks; preflightScopes and preflightEventTypes refactored to validate against callback subscriptions or app event types and to use addonsHintURL + missingScopeAddons/missingSubscriptionAddons for remediation hints; tests added/updated for callback preflight cases and launcher hint URLs.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Bus
  participant Hub
  participant Consumer

  Client->>Bus: HelloFrame (SingleConsumer event key)
  Bus->>Hub: TryRegisterExclusive(subscriptionID)
  alt First subscriber
    Hub-->>Bus: (true, "")
    Bus->>Client: HelloAck (accepted)
    Client->>Consumer: start consuming
  else Existing subscriber
    Hub-->>Bus: (false, "already running PID=12345")
    Bus->>Client: HelloAckRejected (reason)
    Bus->>Bus: close connection
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#654: Refactors event consume/preflight console URL hinting by replacing older helpers with launcher addons deep-link encoding and extends preflight logic with callback subscription checks.
  • larksuite/cli#1289: Overlaps in event consumption/preflight error construction and typed error handling for failed-precondition cases.

Suggested reviewers

  • liangshuo-1
  • liuxinyanglxy
  • heyumeng154-alt

"I nibble bytes and gzip dreams beneath the lunar light,
I base64 the manifest so the launcher scans tonight,
I guard the single-consumer door with a soft polite bite,
Hop, fetch callbacks, and hint — the rabbit waves goodnight. 🐰✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: event subscription optimization including precheck improvements, link generation, and consumer restrictions.
Description check ✅ Passed The description follows the template with all required sections: Summary (clear and concise), Changes (detailed list of modifications), Test Plan (comprehensive test coverage documented), and Related Issues.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/event-subscription-optimize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jun 13, 2026
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.68421% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.42%. Comparing base (72c2947) to head (03d0720).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
cmd/event/consume.go 62.50% 15 Missing ⚠️
cmd/event/console_url.go 69.23% 7 Missing and 5 partials ⚠️
internal/event/bus/hub.go 70.00% 10 Missing and 2 partials ⚠️
internal/appmeta/app_callbacks.go 89.47% 1 Missing and 1 partial ⚠️
internal/event/bus/bus.go 85.71% 1 Missing and 1 partial ⚠️
internal/event/consume/consume.go 75.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1447      +/-   ##
==========================================
+ Coverage   73.35%   73.42%   +0.06%     
==========================================
  Files         750      753       +3     
  Lines       69264    70111     +847     
==========================================
+ Hits        50811    51476     +665     
- Misses      14713    14826     +113     
- Partials     3740     3809      +69     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@03d07207aa36b39db620fdf0cc14f751512dfb4d

🧩 Skill update

npx skills add larksuite/cli#feat/event-subscription-optimize -y -g

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/appmeta/app_callbacks_test.go (1)

24-50: ⚡ Quick win

Add edge/error-path coverage for FetchSubscribedCallbacks.

Current tests only cover success + missing callback_info. Please add cases for:

  1. CallAPI returning error,
  2. malformed JSON (decode error),
  3. callback_info present with missing/null subscribed_callbacks (to lock sentinel behavior).

As per coding guidelines, every behavior change needs a test alongside the change.

🤖 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 `@internal/appmeta/app_callbacks_test.go` around lines 24 - 50, Add unit tests
for FetchSubscribedCallbacks to cover the missing edge/error paths: (1) a test
that simulates CallAPI returning an error (e.g., create fakeCallbackClient that
returns an error and assert FetchSubscribedCallbacks returns that error), (2) a
test for malformed JSON (pass raw like "`{bad json`" and assert a
decode/unmarshal error is returned), and (3) a test where callback_info exists
but subscribed_callbacks is missing or null (e.g., raw with `"callback_info":{}`
and another with `"subscribed_callbacks":null`) and assert the function returns
the sentinel nil slice/behavior currently expected; add these as new
TestFetchSubscribedCallbacks_CallAPIError,
TestFetchSubscribedCallbacks_MalformedJSON, and
TestFetchSubscribedCallbacks_NullSubscribedCallbacks using the same test
patterns as existing tests and the fakeCallbackClient/fetch call to
FetchSubscribedCallbacks.

Source: Coding guidelines

internal/event/registry_test.go (1)

289-301: ⚡ Quick win

Assert the panic reason in the invalid-subtype test.

This currently passes on any panic, including unrelated ones. Reusing mustPanic with a SubscriptionType-specific substring makes the test deterministic for this behavior.

Suggested test tightening
 func TestRegisterKey_InvalidSubscriptionTypePanics(t *testing.T) {
-	defer func() {
-		if r := recover(); r == nil {
-			t.Errorf("expected panic for invalid SubscriptionType")
-		}
-	}()
+	defer mustPanic(t, "SubscriptionType must be")
 	RegisterKey(KeyDefinition{
 		Key:              "test.subtype.bogus",
 		EventType:        "test.subtype.bogus",
 		SubscriptionType: "bogus",
 		Schema:           SchemaDef{Native: &SchemaSpec{Raw: []byte(`{"type":"object"}`)}},
 	})
 }
🤖 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 `@internal/event/registry_test.go` around lines 289 - 301, The test
TestRegisterKey_InvalidSubscriptionTypePanics should assert the panic message to
ensure it fails only for the expected reason; replace the current defer/recover
pattern with a call to the existing mustPanic test helper (invoking RegisterKey
with the bogus SubscriptionType) and assert that the returned panic string
contains a SubscriptionType-specific substring (e.g., "SubscriptionType"); this
uses mustPanic and the RegisterKey call so the test only passes when the panic
is about invalid subscription type.
🤖 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 `@internal/appmeta/app_callbacks.go`:
- Around line 31-37: The function FetchSubscribedCallbacks currently returns
envelope.Data.App.CallbackInfo.SubscribedCallbacks directly, which can be nil
when "subscribed_callbacks" is omitted or null; change FetchSubscribedCallbacks
so when envelope.Data.App.CallbackInfo != nil it returns a non-nil empty slice
if SubscribedCallbacks is nil (e.g., replace direct return with a nil-check and
return []string{} when needed) so preflightEventTypes and pf.subscribedCallbacks
treat the presence of callback_info properly; also add unit tests exercising
FetchSubscribedCallbacks for JSON with "subscribed_callbacks": null and with the
field omitted to assert the returned slice is non-nil (empty) and that
preflightEventTypes enforces missing-callback checks accordingly.

In `@internal/event/consume/reject_test.go`:
- Around line 20-26: The test currently only checks prob.Subtype and message
text; update it to assert full typed error metadata: after calling prob, assert
prob.Category equals the expected category (e.g., errs.CategoryInvalidArgument
or the repo-specific category for this path) in addition to prob.Subtype ==
errs.SubtypeFailedPrecondition, and use errors.As(err, &ve) with ve typed as
*errs.ValidationError to assert the ValidationError.Param equals the expected
param name for this rejection; keep the existing message assertion only as a
supplement, not the primary contract.

---

Nitpick comments:
In `@internal/appmeta/app_callbacks_test.go`:
- Around line 24-50: Add unit tests for FetchSubscribedCallbacks to cover the
missing edge/error paths: (1) a test that simulates CallAPI returning an error
(e.g., create fakeCallbackClient that returns an error and assert
FetchSubscribedCallbacks returns that error), (2) a test for malformed JSON
(pass raw like "`{bad json`" and assert a decode/unmarshal error is returned),
and (3) a test where callback_info exists but subscribed_callbacks is missing or
null (e.g., raw with `"callback_info":{}` and another with
`"subscribed_callbacks":null`) and assert the function returns the sentinel nil
slice/behavior currently expected; add these as new
TestFetchSubscribedCallbacks_CallAPIError,
TestFetchSubscribedCallbacks_MalformedJSON, and
TestFetchSubscribedCallbacks_NullSubscribedCallbacks using the same test
patterns as existing tests and the fakeCallbackClient/fetch call to
FetchSubscribedCallbacks.

In `@internal/event/registry_test.go`:
- Around line 289-301: The test TestRegisterKey_InvalidSubscriptionTypePanics
should assert the panic message to ensure it fails only for the expected reason;
replace the current defer/recover pattern with a call to the existing mustPanic
test helper (invoking RegisterKey with the bogus SubscriptionType) and assert
that the returned panic string contains a SubscriptionType-specific substring
(e.g., "SubscriptionType"); this uses mustPanic and the RegisterKey call so the
test only passes when the panic is about invalid subscription type.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43b5abd5-181b-4b5a-9d4e-ab627f29ef67

📥 Commits

Reviewing files that changed from the base of the PR and between 0fbfe68 and 7748664.

📒 Files selected for processing (18)
  • cmd/event/appmeta_err.go
  • cmd/event/console_url.go
  • cmd/event/console_url_test.go
  • cmd/event/consume.go
  • cmd/event/preflight_test.go
  • internal/appmeta/app_callbacks.go
  • internal/appmeta/app_callbacks_test.go
  • internal/event/bus/bus.go
  • internal/event/bus/handle_hello_test.go
  • internal/event/bus/hub.go
  • internal/event/bus/hub_test.go
  • internal/event/consume/consume.go
  • internal/event/consume/reject_test.go
  • internal/event/protocol/messages.go
  • internal/event/protocol/messages_test.go
  • internal/event/registry.go
  • internal/event/registry_test.go
  • internal/event/types.go

Comment thread internal/appmeta/app_callbacks.go Outdated
Comment thread internal/event/consume/reject_test.go

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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 `@internal/appmeta/app_callbacks_test.go`:
- Around line 44-59: Test coverage is missing for the edge cases where
callback_info exists but subscribed_callbacks is null or omitted; add two unit
tests to app_callbacks_test.go —
TestFetchSubscribedCallbacks_CallbackInfoPresentButNull and
TestFetchSubscribedCallbacks_CallbackInfoPresentButOmitted — that call
FetchSubscribedCallbacks with fakeCallbackClient raw responses reflecting (1)
callback_info with "subscribed_callbacks":null and (2) callback_info present but
without a subscribed_callbacks field, and assert no error, that the returned
slice is non-nil and has length 0 (mirroring the existing
TestFetchSubscribedCallbacks_NoCallbackInfo assertions).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b572df0-e0a4-4165-b549-929446e421de

📥 Commits

Reviewing files that changed from the base of the PR and between 7748664 and c39b567.

📒 Files selected for processing (3)
  • cmd/event/preflight_test.go
  • internal/appmeta/app_callbacks.go
  • internal/appmeta/app_callbacks_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/event/preflight_test.go

Comment thread internal/appmeta/app_callbacks_test.go
@sang-neo03
sang-neo03 merged commit ed7fdd1 into main Jun 16, 2026
20 checks passed
@sang-neo03
sang-neo03 deleted the feat/event-subscription-optimize branch June 16, 2026 11:41
@coderabbitai coderabbitai Bot mentioned this pull request Jun 17, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants