Skip to content

feat(trogon-source-linear): add Linear webhook receiver#92

Merged
yordis merged 1 commit intomainfrom
Linear
Apr 5, 2026
Merged

feat(trogon-source-linear): add Linear webhook receiver#92
yordis merged 1 commit intomainfrom
Linear

Conversation

@yordis
Copy link
Copy Markdown
Member

@yordis yordis commented Apr 5, 2026

  • Linear webhooks need a receiver that validates HMAC signatures and sinks events into NATS JetStream, following the same pattern as trogon-source-github and trogon-source-slack
  • Named trogon-source-linear to match the existing trogon-source-* naming convention
  • Workspace dependencies used for async-nats (0.47) to avoid dual-version conflicts

@cursor
Copy link
Copy Markdown

cursor bot commented Apr 5, 2026

PR Summary

Medium Risk
Adds a new externally-facing webhook service that performs signature validation, replay protection, and JetStream publishing; misconfiguration or parsing errors could drop/accept unexpected events, but changes are largely additive and isolated.

Overview
Adds a new trogon-source-linear Rust crate and Docker Compose service that exposes POST /webhook and GET /health, validates Linear’s linear-signature HMAC, optionally enforces a webhookTimestamp tolerance, and publishes the raw JSON payload to JetStream on linear.{type}.{action} with Nats-Msg-Id set to webhookId for dedup.

Updates local dev plumbing to run it: new .env knobs, a multi-stage Dockerfile, an ngrok tunnel entry, and an acp-telemetry ServiceName entry. Cargo lockfile is updated to include the new crate and its added test/dev dependencies.

Reviewed by Cursor Bugbot for commit 8f01a0e. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Warning

Rate limit exceeded

@yordis has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 40 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 8 minutes and 40 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a5cc2b9e-7bcb-4169-813a-e577022f5bb0

📥 Commits

Reviewing files that changed from the base of the PR and between 87d1d03 and 8f01a0e.

⛔ Files ignored due to path filters (1)
  • rsworkspace/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • devops/docker/compose/.env.example
  • devops/docker/compose/compose.yml
  • devops/docker/compose/services/trogon-source-linear/Dockerfile
  • devops/docker/compose/services/trogon-source-linear/README.md
  • rsworkspace/crates/acp-telemetry/src/service_name.rs
  • rsworkspace/crates/trogon-source-linear/Cargo.toml
  • rsworkspace/crates/trogon-source-linear/src/config.rs
  • rsworkspace/crates/trogon-source-linear/src/constants.rs
  • rsworkspace/crates/trogon-source-linear/src/lib.rs
  • rsworkspace/crates/trogon-source-linear/src/main.rs
  • rsworkspace/crates/trogon-source-linear/src/server.rs
  • rsworkspace/crates/trogon-source-linear/src/signature.rs

Walkthrough

Adds a new Linear webhook receiver crate and Docker Compose service (trogon-source-linear) that validates HMAC signatures, enforces timestamp replay tolerance, and publishes webhook payloads to NATS JetStream subjects.

Changes

Cohort / File(s) Summary
Compose & Env
devops/docker/compose/.env.example, devops/docker/compose/compose.yml
Added commented Linear env vars to .env.example. Added trogon-source-linear service, healthcheck, ngrok tunnel and ngrok dependency on the Linear service.
Dockerfile & README
devops/docker/compose/services/trogon-source-linear/Dockerfile, .../README.md
New multi-stage Dockerfile using cargo-chef and a slim runtime; README documents local setup, ngrok usage, webhook configuration, and env var references.
Crate Manifest & Entrypoint
rsworkspace/crates/trogon-source-linear/Cargo.toml, .../src/main.rs, .../src/lib.rs
New crate manifest and binary; runtime dependencies (async-nats, axum, tokio, tracing, workspace crates). main initializes logging, loads config, connects to NATS, and starts the server. lib.rs re-exports config and serve.
Configuration & Constants
rsworkspace/crates/trogon-source-linear/src/config.rs, .../src/constants.rs
LinearConfig::from_env parses webhook secret, port, subject/stream settings, timestamp tolerance, and JetStream/NATS timeouts with defaults and extensive unit tests. Added typed default constants.
Server & Signature
rsworkspace/crates/trogon-source-linear/src/server.rs, .../src/signature.rs
Added Axum server with GET /health and POST /webhook, signature verification (HMAC-SHA256), timestamp replay checks, subject validation, JetStream publish with headers, ack/stream-op timeouts, retry and stream recreation logic, and unit tests for validation/signature.

Sequence Diagram

sequenceDiagram
    actor Client as Linear API
    participant Server as HTTP Server<br/>(trogon-source-linear)
    participant Sig as Signature Module
    participant NATS as JetStream

    Client->>Server: POST /webhook (body + linear-signature)
    Server->>Server: Read header, parse body, check timestamp
    Server->>Sig: verify(secret, body, signature)
    Sig-->>Server: valid / invalid
    alt valid
        Server->>Server: validate type & action tokens
        Server->>NATS: Publish to {prefix}.{type}.{action} with headers
        NATS-->>Server: Ack / Timeout
        alt Ack success
            Server-->>Client: 200 OK
        else Ack fail
            Server->>NATS: attempt stream recreation (stream_op_timeout)
            Server->>NATS: retry publish (ack_timeout)
            alt retry success
                Server-->>Client: 200 OK
            else
                Server-->>Client: 500
            end
        end
    else invalid
        Server-->>Client: 400/401
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰
I nibble on secrets in the night,
Signatures checked, NATS streams alight,
Docker hops, ngrok makes a way,
Webhooks land and safely stay,
Hooray — a linear field of bytes!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: adding a Linear webhook receiver as a new source integration.
Description check ✅ Passed The description explains the rationale, naming convention, and technical approach (HMAC validation, NATS JetStream, pattern matching) which all relate to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 Linear

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.

@yordis yordis force-pushed the Linear branch 4 times, most recently from 889ab51 to 174a2cc Compare April 5, 2026 19:01
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 5, 2026

badge

Code Coverage Summary

Details
Filename                                                                      Stmts    Miss  Cover    Missing
--------------------------------------------------------------------------  -------  ------  -------  ---------------------------------------------------------------------------------------------
crates/trogon-nats/src/messaging.rs                                             552       2  99.64%   132, 142
crates/trogon-nats/src/nats_token.rs                                            161       0  100.00%
crates/trogon-nats/src/client.rs                                                 25      25  0.00%    50-89
crates/trogon-nats/src/token.rs                                                   8       0  100.00%
crates/trogon-nats/src/connect.rs                                               105      11  89.52%   22-24, 37, 49, 68-73
crates/trogon-nats/src/mocks.rs                                                 304       0  100.00%
crates/trogon-nats/src/auth.rs                                                  119       0  100.00%
crates/acp-nats-stdio/src/main.rs                                               141      27  80.85%   62, 114-121, 127-129, 146, 177-198
crates/acp-nats-stdio/src/config.rs                                              72       0  100.00%
crates/trogon-source-linear/src/signature.rs                                     54       1  98.15%   16
crates/trogon-source-linear/src/config.rs                                       208       0  100.00%
crates/trogon-source-linear/src/main.rs                                           4       0  100.00%
crates/trogon-source-linear/src/server.rs                                        42      42  0.00%    55-245
crates/acp-telemetry/src/service_name.rs                                         31       0  100.00%
crates/acp-telemetry/src/signal.rs                                                3       3  0.00%    4-43
crates/acp-telemetry/src/trace.rs                                                32       4  87.50%   23-24, 31-32
crates/acp-telemetry/src/lib.rs                                                 153      22  85.62%   39-46, 81, 86, 91, 105-120
crates/acp-telemetry/src/metric.rs                                               35       4  88.57%   30-31, 38-39
crates/acp-telemetry/src/log.rs                                                  70       2  97.14%   39-40
crates/acp-nats/src/nats/subjects/stream.rs                                      58       0  100.00%
crates/acp-nats/src/nats/subjects/mod.rs                                        380       0  100.00%
crates/trogon-source-slack/src/signature.rs                                      80       0  100.00%
crates/trogon-source-slack/src/server.rs                                        925       3  99.68%   119-121
crates/trogon-source-slack/src/config.rs                                        163       0  100.00%
crates/trogon-source-slack/src/main.rs                                            4       0  100.00%
crates/acp-nats-agent/src/connection.rs                                        1434       1  99.93%   686
crates/acp-nats/src/nats/subjects/commands/cancel.rs                             18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/close.rs                              18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/set_mode.rs                           18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/set_model.rs                          18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/prompt.rs                             18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/load.rs                               18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/fork.rs                               18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/resume.rs                             18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/set_config_option.rs                  18       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/all_agent_ext.rs                 11       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/all_agent.rs                     11       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/prompt_wildcard.rs               11       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/one_session.rs                   18       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/one_agent.rs                     18       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/one_client.rs                    18       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/all_client.rs                    11       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/global_all.rs                    11       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/all_session.rs                   11       0  100.00%
crates/trogon-nats/src/jetstream/publish.rs                                      57       0  100.00%
crates/trogon-nats/src/jetstream/mocks.rs                                       474       0  100.00%
crates/acp-nats/src/nats/subjects/responses/cancelled.rs                         18       0  100.00%
crates/acp-nats/src/nats/subjects/responses/update.rs                            27       0  100.00%
crates/acp-nats/src/nats/subjects/responses/prompt_response.rs                   27       0  100.00%
crates/acp-nats/src/nats/subjects/responses/ext_ready.rs                         15       0  100.00%
crates/acp-nats/src/nats/subjects/responses/response.rs                          20       0  100.00%
crates/trogon-source-github/src/signature.rs                                     64       0  100.00%
crates/trogon-source-github/src/main.rs                                           4       0  100.00%
crates/trogon-source-github/src/config.rs                                       104       0  100.00%
crates/trogon-source-github/src/server.rs                                       368       0  100.00%
crates/acp-nats/src/nats/mod.rs                                                  23       0  100.00%
crates/acp-nats/src/nats/extensions.rs                                            3       0  100.00%
crates/acp-nats/src/nats/parsing.rs                                             285       1  99.65%   153
crates/acp-nats/src/jetstream/consumers.rs                                       99       0  100.00%
crates/acp-nats/src/jetstream/streams.rs                                        194       4  97.94%   254-256, 266
crates/acp-nats/src/jetstream/ext_policy.rs                                      26       0  100.00%
crates/acp-nats/src/jetstream/provision.rs                                       61       0  100.00%
crates/acp-nats/src/nats/subjects/global/session_new.rs                           8       0  100.00%
crates/acp-nats/src/nats/subjects/global/authenticate.rs                          8       0  100.00%
crates/acp-nats/src/nats/subjects/global/ext_notify.rs                           12       0  100.00%
crates/acp-nats/src/nats/subjects/global/ext.rs                                  12       0  100.00%
crates/acp-nats/src/nats/subjects/global/initialize.rs                            8       0  100.00%
crates/acp-nats/src/nats/subjects/global/session_list.rs                          8       0  100.00%
crates/acp-nats/src/nats/subjects/global/logout.rs                                8       0  100.00%
crates/acp-nats/src/session_id.rs                                                72       0  100.00%
crates/acp-nats/src/error.rs                                                     84       0  100.00%
crates/acp-nats/src/ext_method_name.rs                                           70       0  100.00%
crates/acp-nats/src/in_flight_slot_guard.rs                                      32       0  100.00%
crates/acp-nats/src/req_id.rs                                                    39       0  100.00%
crates/acp-nats/src/client_proxy.rs                                             200       0  100.00%
crates/acp-nats/src/acp_prefix.rs                                                51       0  100.00%
crates/acp-nats/src/config.rs                                                   204       0  100.00%
crates/acp-nats/src/jsonrpc.rs                                                    6       0  100.00%
crates/acp-nats/src/lib.rs                                                       73       0  100.00%
crates/acp-nats/src/pending_prompt_waiters.rs                                   141       0  100.00%
crates/acp-nats-ws/src/connection.rs                                            166      35  78.92%   75-82, 87-98, 114, 116-117, 122, 133-135, 142, 146, 150, 153-161, 172, 176, 179, 182-186, 220
crates/acp-nats-ws/src/config.rs                                                 83       0  100.00%
crates/acp-nats-ws/src/upgrade.rs                                                57       2  96.49%   59, 90
crates/acp-nats-ws/src/main.rs                                                  187      18  90.37%   87, 204-225, 303
crates/trogon-std/src/fs/mem.rs                                                 220      10  95.45%   61-63, 77-79, 133-135, 158
crates/trogon-std/src/fs/system.rs                                               29      12  58.62%   17-19, 31-45
crates/trogon-std/src/json.rs                                                    30       0  100.00%
crates/trogon-std/src/args.rs                                                    10       0  100.00%
crates/acp-nats/src/client/terminal_kill.rs                                     309       0  100.00%
crates/acp-nats/src/client/terminal_output.rs                                   223       0  100.00%
crates/acp-nats/src/client/terminal_release.rs                                  357       0  100.00%
crates/acp-nats/src/client/terminal_wait_for_exit.rs                            396       0  100.00%
crates/acp-nats/src/client/rpc_reply.rs                                          71       0  100.00%
crates/acp-nats/src/client/ext.rs                                               365       8  97.81%   193-204, 229-240
crates/acp-nats/src/client/ext_session_prompt_response.rs                       157       0  100.00%
crates/acp-nats/src/client/fs_read_text_file.rs                                 384       0  100.00%
crates/acp-nats/src/client/mod.rs                                              2987       0  100.00%
crates/acp-nats/src/client/request_permission.rs                                338       0  100.00%
crates/acp-nats/src/client/fs_write_text_file.rs                                451       0  100.00%
crates/acp-nats/src/client/session_update.rs                                     55       0  100.00%
crates/acp-nats/src/client/terminal_create.rs                                   294       0  100.00%
crates/acp-nats/src/telemetry/metrics.rs                                         65       0  100.00%
crates/trogon-std/src/dirs/system.rs                                             98      11  88.78%   57, 65, 67, 75, 77, 85, 87, 96, 98, 109, 154
crates/trogon-std/src/dirs/fixed.rs                                              84       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/fs_write_text_file.rs               15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/session_update.rs                   15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/terminal_create.rs                  15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/fs_read_text_file.rs                15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/terminal_release.rs                 15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/terminal_output.rs                  15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/terminal_kill.rs                    15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/terminal_wait_for_exit.rs           15       0  100.00%
crates/acp-nats/src/nats/subjects/client_ops/session_request_permission.rs       15       0  100.00%
crates/trogon-std/src/env/system.rs                                              17       0  100.00%
crates/trogon-std/src/env/in_memory.rs                                           81       0  100.00%
crates/trogon-std/src/time/mock.rs                                              129       0  100.00%
crates/trogon-std/src/time/system.rs                                             27       3  88.89%   27-29
crates/acp-nats/src/agent/initialize.rs                                          83       0  100.00%
crates/acp-nats/src/agent/cancel.rs                                             105       0  100.00%
crates/acp-nats/src/agent/set_session_config_option.rs                           71       0  100.00%
crates/acp-nats/src/agent/set_session_mode.rs                                    71       0  100.00%
crates/acp-nats/src/agent/mod.rs                                                 65       0  100.00%
crates/acp-nats/src/agent/authenticate.rs                                        52       0  100.00%
crates/acp-nats/src/agent/prompt.rs                                             633       0  100.00%
crates/acp-nats/src/agent/close_session.rs                                       67       0  100.00%
crates/acp-nats/src/agent/ext_notification.rs                                    88       0  100.00%
crates/acp-nats/src/agent/ext_method.rs                                          92       0  100.00%
crates/acp-nats/src/agent/fork_session.rs                                       106       0  100.00%
crates/acp-nats/src/agent/new_session.rs                                         91       0  100.00%
crates/acp-nats/src/agent/load_session.rs                                       101       0  100.00%
crates/acp-nats/src/agent/resume_session.rs                                     102       0  100.00%
crates/acp-nats/src/agent/set_session_model.rs                                   71       0  100.00%
crates/acp-nats/src/agent/test_support.rs                                       299       0  100.00%
crates/acp-nats/src/agent/logout.rs                                              49       0  100.00%
crates/acp-nats/src/agent/bridge.rs                                             123       4  96.75%   109-112
crates/acp-nats/src/agent/list_sessions.rs                                       50       0  100.00%
crates/acp-nats/src/agent/js_request.rs                                         304       0  100.00%
TOTAL                                                                         18778     255  98.64%

Diff against main

Filename                                        Stmts    Miss  Cover
--------------------------------------------  -------  ------  --------
crates/trogon-source-linear/src/signature.rs      +54      +1  +98.15%
crates/trogon-source-linear/src/config.rs        +208       0  +100.00%
crates/trogon-source-linear/src/main.rs            +4       0  +100.00%
crates/trogon-source-linear/src/server.rs         +42     +42  +100.00%
crates/acp-telemetry/src/service_name.rs           +5       0  +100.00%
TOTAL                                            +313     +43  -0.21%

Results for commit: 8f01a0e

Minimum allowed coverage is 95%

♻️ This comment has been updated with latest results

@yordis yordis force-pushed the Linear branch 4 times, most recently from dddb064 to d94cf9b Compare April 5, 2026 19:09
@yordis yordis force-pushed the Linear branch 2 times, most recently from a01f323 to 739bbdc Compare April 5, 2026 19:24
Copy link
Copy Markdown

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@devops/docker/compose/compose.yml`:
- Line 51: The compose.yml currently defaults LINEAR_WEBHOOK_SECRET to an empty
string which allows predictable empty-key signature checks; remove the fallback
so the service uses the raw environment value (use LINEAR_WEBHOOK_SECRET without
":-" default) and/or add startup validation in the application entrypoint (check
process.env.LINEAR_WEBHOOK_SECRET) to fail fast if LINEAR_WEBHOOK_SECRET is
unset, ensuring webhook signature verification always uses a real secret.

In `@rsworkspace/crates/trogon-source-linear/src/config.rs`:
- Around line 24-25: LinearConfig currently allows webhook_secret:
Option<String>, which lets the receiver run open or accept empty keys; instead
enforce a non-empty secret at startup by requiring and validating
LINEAR_WEBHOOK_SECRET when constructing LinearConfig (change the field to a
plain String or keep Option but fail if None or empty). Update the config loader
where LinearConfig is built (refer to LinearConfig and webhook_secret) to
error/exit if the env var is missing or equals "" and adjust handle_webhook to
assume a validated non-empty secret (or keep current check but it will never see
None/empty after validation).
- Around line 27-28: The config currently accepts empty strings for
subject_prefix and stream_name (pub subject_prefix: String, pub stream_name:
String), which lets invalid env values slip through; update the Config
construction/factory (the constructor or load_from_env function that produces
this struct) to validate these fields: if the env value is empty or whitespace
either replace it with the intended default or return an error so invalid
configs are rejected at load time, and prefer wrapping these fields in small
domain types (e.g., AcpPrefix or LinearStreamName) whose constructors enforce
non-empty/format rules so invalid instances are unrepresentable; apply the same
validation for the other fields noted around lines 56–61.

In `@rsworkspace/crates/trogon-source-linear/src/server.rs`:
- Around line 35-37: The initial ensure_stream(&js, &config.stream_name,
&config.subject_prefix, config.stream_max_age).await call can block
indefinitely; wrap that call in the same timeout used on recovery
(LINEAR_NATS_STREAM_OP_TIMEOUT_MS) via
tokio::time::timeout(Duration::from_millis(LINEAR_NATS_STREAM_OP_TIMEOUT_MS),
...) and propagate a suitable error if the timeout elapses so startup fails
fast; apply this change where jetstream::new(nats) gives js and ensure_stream is
invoked so startup uses the identical stream-op timeout as the recovery path.
- Around line 258-260: The replay-protection currently skips verification when
webhookTimestamp is missing because the combined condition lets payloads
through; change the logic so that when state.timestamp_tolerance is
Some(tolerance) you require a parseable timestamp from
parsed.get("webhookTimestamp").and_then(|v| v.as_u64()) and reject the request
if that timestamp is absent or malformed. Concretely, in the handler around
state.timestamp_tolerance and parsed.get("webhookTimestamp") (look for the if
let involving tolerance and ts_ms), first match/if let on
state.timestamp_tolerance => Some(tolerance) and then attempt to extract ts_ms
separately; if ts_ms is None return an error (or reject the request) instead of
skipping the replay check, otherwise perform the existing tolerance check using
tolerance and ts_ms. Ensure you reference state.timestamp_tolerance,
parsed.get("webhookTimestamp"), ts_ms and tolerance in the updated flow.
- Line 5: AppState currently stores raw async_nats::jetstream::Context and
handlers call .publish_with_headers() directly, bypassing trogon_nats
abstractions; change AppState to hold a dyn trogon_nats::PublishClient (or
generic over P: PublishClient), update the webhook publisher function signature
to accept that PublishClient (or generic P) so tests can inject mocks and
standard retry/reconnect logic is used (update usages where connect() is called
to extract the PublishClient); wrap the initial ensure_stream() call inside the
configured config.stream_op_timeout similarly to the recovery path so startup
uses the same timeout; and tighten the replay protection by requiring
webhookTimestamp whenever config.timestamp_tolerance is Some—modify the guard in
the webhook handler (the code around webhookTimestamp/timestamp_tolerance
checks) to reject requests missing webhookTimestamp when tolerance is
configured.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f541e5a1-dfb3-4a3c-b7c4-e1e2c68c9484

📥 Commits

Reviewing files that changed from the base of the PR and between d821ac0 and a822a64.

⛔ Files ignored due to path filters (1)
  • rsworkspace/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • devops/docker/compose/.env.example
  • devops/docker/compose/compose.yml
  • devops/docker/compose/services/trogon-source-linear/Dockerfile
  • devops/docker/compose/services/trogon-source-linear/README.md
  • rsworkspace/crates/trogon-source-linear/Cargo.toml
  • rsworkspace/crates/trogon-source-linear/src/config.rs
  • rsworkspace/crates/trogon-source-linear/src/constants.rs
  • rsworkspace/crates/trogon-source-linear/src/lib.rs
  • rsworkspace/crates/trogon-source-linear/src/main.rs
  • rsworkspace/crates/trogon-source-linear/src/server.rs
  • rsworkspace/crates/trogon-source-linear/src/signature.rs

@yordis yordis force-pushed the Linear branch 2 times, most recently from 87d1d03 to 5fc0301 Compare April 5, 2026 20:01
Copy link
Copy Markdown

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

♻️ Duplicate comments (2)
rsworkspace/crates/trogon-source-linear/src/config.rs (2)

60-65: ⚠️ Potential issue | 🟠 Major

Reject or normalize blank LINEAR_SUBJECT_PREFIX / LINEAR_STREAM_NAME at load time.

Line 60 and Line 63 accept empty env values as-is, which defers failure into runtime stream/subject operations. Normalize blanks to defaults (or hard-fail) in from_env.

🧹 Proposed fix
             subject_prefix: env
                 .var("LINEAR_SUBJECT_PREFIX")
-                .unwrap_or_else(|_| DEFAULT_SUBJECT_PREFIX.to_string()),
+                .ok()
+                .map(|v| v.trim().to_owned())
+                .filter(|v| !v.is_empty())
+                .unwrap_or_else(|| DEFAULT_SUBJECT_PREFIX.to_string()),
             stream_name: env
                 .var("LINEAR_STREAM_NAME")
-                .unwrap_or_else(|_| DEFAULT_STREAM_NAME.to_string()),
+                .ok()
+                .map(|v| v.trim().to_owned())
+                .filter(|v| !v.is_empty())
+                .unwrap_or_else(|| DEFAULT_STREAM_NAME.to_string()),

As per coding guidelines, "Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String), with each type's factory guaranteeing correctness at construction—invalid instances should be unrepresentable".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/trogon-source-linear/src/config.rs` around lines 60 - 65,
The code currently accepts empty env vars for LINEAR_SUBJECT_PREFIX and
LINEAR_STREAM_NAME in the from_env constructor when initializing subject_prefix
and stream_name; update from_env so it trims the env value and either
substitutes DEFAULT_SUBJECT_PREFIX / DEFAULT_STREAM_NAME when the trimmed value
is empty or return an Err to fail fast; implement this normalization/validation
in the subject_prefix and stream_name creation (or delegate to domain-specific
constructors like AcpPrefix/AcpStream) so invalid blank strings are rejected at
load time rather than later in stream/subject operations.

50-54: ⚠️ Potential issue | 🟠 Major

Trim LINEAR_WEBHOOK_SECRET before emptiness validation.

Line 53 only rejects ""; " " still passes and boots with an unusable secret. Fail fast on whitespace-only values too.

🔒 Proposed fix
             webhook_secret: env
                 .var("LINEAR_WEBHOOK_SECRET")
                 .ok()
-                .filter(|s| !s.is_empty())
+                .map(|s| s.trim().to_owned())
+                .filter(|s| !s.is_empty())
                 .expect("LINEAR_WEBHOOK_SECRET is required"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/trogon-source-linear/src/config.rs` around lines 50 - 54,
The webhook_secret construction currently only rejects empty strings but accepts
whitespace-only values; update the retrieval of LINEAR_WEBHOOK_SECRET (the
env.var("LINEAR_WEBHOOK_SECRET") chain that builds webhook_secret) to trim the
string before checking emptiness—i.e., map or filter the option to .trim() (or
call .map(str::trim).filter(|s| !s.is_empty())) so whitespace-only values are
rejected and expect(...) still triggers for missing/blank secrets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@rsworkspace/crates/trogon-source-linear/src/config.rs`:
- Around line 60-65: The code currently accepts empty env vars for
LINEAR_SUBJECT_PREFIX and LINEAR_STREAM_NAME in the from_env constructor when
initializing subject_prefix and stream_name; update from_env so it trims the env
value and either substitutes DEFAULT_SUBJECT_PREFIX / DEFAULT_STREAM_NAME when
the trimmed value is empty or return an Err to fail fast; implement this
normalization/validation in the subject_prefix and stream_name creation (or
delegate to domain-specific constructors like AcpPrefix/AcpStream) so invalid
blank strings are rejected at load time rather than later in stream/subject
operations.
- Around line 50-54: The webhook_secret construction currently only rejects
empty strings but accepts whitespace-only values; update the retrieval of
LINEAR_WEBHOOK_SECRET (the env.var("LINEAR_WEBHOOK_SECRET") chain that builds
webhook_secret) to trim the string before checking emptiness—i.e., map or filter
the option to .trim() (or call .map(str::trim).filter(|s| !s.is_empty())) so
whitespace-only values are rejected and expect(...) still triggers for
missing/blank secrets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6055106b-b16a-46a9-bca3-bf29fa9d7ba8

📥 Commits

Reviewing files that changed from the base of the PR and between a822a64 and 87d1d03.

⛔ Files ignored due to path filters (1)
  • rsworkspace/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • devops/docker/compose/.env.example
  • devops/docker/compose/compose.yml
  • devops/docker/compose/services/trogon-source-linear/Dockerfile
  • devops/docker/compose/services/trogon-source-linear/README.md
  • rsworkspace/crates/trogon-source-linear/Cargo.toml
  • rsworkspace/crates/trogon-source-linear/src/config.rs
  • rsworkspace/crates/trogon-source-linear/src/constants.rs
  • rsworkspace/crates/trogon-source-linear/src/lib.rs
  • rsworkspace/crates/trogon-source-linear/src/main.rs
  • rsworkspace/crates/trogon-source-linear/src/server.rs
  • rsworkspace/crates/trogon-source-linear/src/signature.rs
✅ Files skipped from review due to trivial changes (7)
  • devops/docker/compose/.env.example
  • rsworkspace/crates/trogon-source-linear/src/main.rs
  • devops/docker/compose/services/trogon-source-linear/README.md
  • rsworkspace/crates/trogon-source-linear/src/constants.rs
  • rsworkspace/crates/trogon-source-linear/src/lib.rs
  • rsworkspace/crates/trogon-source-linear/Cargo.toml
  • devops/docker/compose/services/trogon-source-linear/Dockerfile
🚧 Files skipped from review as they are similar to previous changes (3)
  • rsworkspace/crates/trogon-source-linear/src/signature.rs
  • devops/docker/compose/compose.yml
  • rsworkspace/crates/trogon-source-linear/src/server.rs

@yordis yordis force-pushed the Linear branch 6 times, most recently from 11d378e to f6ed725 Compare April 5, 2026 20:23
@yordis yordis force-pushed the Linear branch 4 times, most recently from f1dc828 to e5bd6c6 Compare April 5, 2026 20:33
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e5bd6c6. Configure here.

@yordis yordis force-pushed the Linear branch 4 times, most recently from 99c874c to c69b0f7 Compare April 5, 2026 20:47
…ents to NATS JetStream

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis added the rust:coverage-baseline-reset Relax Rust coverage gate to establish a new baseline label Apr 5, 2026
@yordis yordis merged commit a865cf0 into main Apr 5, 2026
9 of 10 checks passed
@yordis yordis deleted the Linear branch April 5, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rust:coverage-baseline-reset Relax Rust coverage gate to establish a new baseline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant