Skip to content

feat: add incident.io gateway source#114

Merged
yordis merged 1 commit intomainfrom
yordis/incidentio-gateway
Apr 11, 2026
Merged

feat: add incident.io gateway source#114
yordis merged 1 commit intomainfrom
yordis/incidentio-gateway

Conversation

@yordis
Copy link
Copy Markdown
Member

@yordis yordis commented Apr 10, 2026

Summary

  • add a new trogon-source-incidentio crate for Svix-signed incident.io webhooks
  • wire incident.io into trogon-gateway config, routing, stream provisioning, compose env, and gateway docs
  • preserve dotted event_type routing, set Nats-Msg-Id from webhook ids, and publish authenticated malformed payloads to incidentio.unroutable

Testing

  • CARGO_TARGET_DIR=/tmp/munich-target-incidentio mise exec -- cargo test -p trogon-source-incidentio --offline
  • CARGO_TARGET_DIR=/tmp/munich-target-gateway mise exec -- cargo test -p trogon-gateway --offline

@cursor
Copy link
Copy Markdown

cursor bot commented Apr 10, 2026

PR Summary

Medium Risk
Adds a new externally-facing webhook receiver with signature verification and NATS publishing; mistakes could cause dropped/unauthorized events or misrouted subjects, but changes are largely additive and well-covered by tests.

Overview
Adds incident.io as a new trogon-gateway source, including config/env wiring, HTTP route mounting at /incidentio/webhook, and JetStream stream provisioning.

Introduces a new trogon-source-incidentio crate that verifies Svix-style HMAC signatures (with timestamp tolerance), routes events to incidentio.<event_type> subjects (preserving dotted event_type), sets Nats-Msg-Id from the webhook id for dedupe, and publishes authenticated-but-unroutable payloads to incidentio.unroutable with reject-reason headers.

Updates Docker Compose .env.example/compose.yml and gateway docs to include the new source, and refactors gateway config validation to return structured ConfigValidationError values (used for incident.io and existing sources) with improved error formatting and expanded tests.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

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

Adds a new incident.io webhook source crate and integrates it into trogon-gateway: new crate, config/validation types, signature verification, Axum POST /incidentio/webhook router, JetStream provisioning and claim-check publishing, Docker compose/env entries, documentation, and tests.

Changes

Cohort / File(s) Summary
Compose & docs
devops/docker/compose/.env.example, devops/docker/compose/compose.yml, devops/docker/compose/services/trogon-gateway/README.md
Added commented env placeholders and compose env entries for INCIDENTIO; documented /incidentio/webhook endpoint, signing secret, and source routing semantics.
Workspace & gateway deps
rsworkspace/Cargo.toml, rsworkspace/crates/trogon-gateway/Cargo.toml
Added trogon-source-incidentio to the workspace and as a dependency of trogon-gateway.
Gateway config, HTTP, streams
rsworkspace/crates/trogon-gateway/src/config.rs, .../http.rs, .../streams.rs
Introduced typed ConfigValidationError, added optional incidentio resolved config, updated resolvers/tests, conditional mount of /incidentio router, and conditional provisioning of the Incidentio stream; updated test fixtures.
New crate manifest & root
rsworkspace/crates/trogon-source-incidentio/Cargo.toml, .../src/lib.rs
New crate manifest and lib root exposing config, constants, event/signing types, signature verifier, server router, and provision function.
Config & constants
rsworkspace/crates/trogon-source-incidentio/src/config.rs, .../constants.rs
Added IncidentioConfig and compile-time HTTP/body/header constants.
Signing secret & event type
rsworkspace/crates/trogon-source-incidentio/src/incidentio_signing_secret.rs, .../incidentio_event_type.rs
Added validated, redacted Base64 IncidentioSigningSecret type and IncidentioEventType (dotted NATS token) with explicit error types and tests.
Signature verification
rsworkspace/crates/trogon-source-incidentio/src/signature.rs
Implemented Svix-style HMAC-SHA256 verification with detailed SignatureError, typed webhook id/timestamp, timestamp tolerance checks, parsing of multiple signature entries, and comprehensive tests.
Server & publishing
rsworkspace/crates/trogon-source-incidentio/src/server.rs
Axum router and POST /webhook handler: verify signature, parse/validate event_type, publish via ClaimCheckPublisher to <prefix>.<event_type> or <prefix>.unroutable with metadata headers; includes stream provisioning and many unit tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/incident.io
    participant Handler as Webhook Handler
    participant Verifier as Signature Verifier
    participant Parser as JSON Parser
    participant Validator as EventType Validator
    participant Publisher as ClaimCheckPublisher
    participant JetStream as NATS JetStream

    Client->>Handler: POST /incidentio/webhook (headers + body)
    Handler->>Verifier: verify(headers, body, signing_secret, tolerance)
    alt signature invalid
        Verifier-->>Handler: SignatureError
        Handler-->>Client: 401 Unauthorized
    else signature valid
        Verifier-->>Handler: VerifiedWebhook{id,timestamp}
        Handler->>Parser: parse body as JSON
        alt invalid JSON
            Parser-->>Handler: ParseError
            Handler->>Publisher: publish to <prefix>.unroutable (with reject header)
            Publisher->>JetStream: publish claim-check
            JetStream-->>Publisher: ack / error
            Handler-->>Client: 200 OK / 500 on publish error
        else valid JSON
            Parser-->>Handler: {event_type,...}
            Handler->>Validator: validate event_type
            alt invalid event_type
                Validator-->>Handler: IncidentioEventTypeError
                Handler->>Publisher: publish to <prefix>.unroutable (with reject header)
                Publisher->>JetStream: publish
                JetStream-->>Publisher: ack / error
                Handler-->>Client: 200 OK / 500 on publish error
            else valid event_type
                Validator-->>Handler: IncidentioEventType
                Handler->>Publisher: publish original body to <prefix>.<event_type> (with headers)
                Publisher->>JetStream: publish claim-check
                JetStream-->>Publisher: ack / error
                alt publish success
                    Handler-->>Client: 200 OK
                else publish failure
                    Handler-->>Client: 500 Internal Server Error
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested labels

rust:coverage-baseline-reset

Poem

🐰 I found a whsec_ tucked in my nest,
I checked each HMAC and did my best,
/incidentio/webhook hopped through the night,
NATS kept the claim-check, headers tight,
A stream was born — I thumped with delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding incident.io as a new gateway source integration.
Description check ✅ Passed The PR description is directly related to the changeset, providing specific implementation details and testing commands that align with the changes shown in the file summaries.

✏️ 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 yordis/incidentio-gateway

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
Copy link
Copy Markdown

github-actions bot commented Apr 10, 2026

badge

Code Coverage Summary

Details
Filename                                                                      Stmts    Miss  Cover    Missing
--------------------------------------------------------------------------  -------  ------  -------  ---------------------------------------------------------------------------------------------
crates/trogon-source-telegram/src/signature.rs                                   38       0  100.00%
crates/trogon-source-telegram/src/config.rs                                      17       0  100.00%
crates/trogon-source-telegram/src/server.rs                                     387       0  100.00%
crates/trogon-nats/src/nats_token.rs                                            161       0  100.00%
crates/trogon-nats/src/messaging.rs                                             552       2  99.64%   132, 142
crates/trogon-nats/src/client.rs                                                 25      25  0.00%    50-89
crates/trogon-nats/src/auth.rs                                                  119       0  100.00%
crates/trogon-nats/src/mocks.rs                                                 304       0  100.00%
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/acp-nats/src/agent/close_session.rs                                       67       0  100.00%
crates/acp-nats/src/agent/ext_method.rs                                          92       0  100.00%
crates/acp-nats/src/agent/set_session_config_option.rs                           71       0  100.00%
crates/acp-nats/src/agent/cancel.rs                                             105       0  100.00%
crates/acp-nats/src/agent/ext_notification.rs                                    88       0  100.00%
crates/acp-nats/src/agent/load_session.rs                                       101       0  100.00%
crates/acp-nats/src/agent/test_support.rs                                       299       0  100.00%
crates/acp-nats/src/agent/fork_session.rs                                       106       0  100.00%
crates/acp-nats/src/agent/set_session_mode.rs                                    71       0  100.00%
crates/acp-nats/src/agent/set_session_model.rs                                   71       0  100.00%
crates/acp-nats/src/agent/js_request.rs                                         304       0  100.00%
crates/acp-nats/src/agent/resume_session.rs                                     102       0  100.00%
crates/acp-nats/src/agent/bridge.rs                                             123       4  96.75%   109-112
crates/acp-nats/src/agent/mod.rs                                                 65       0  100.00%
crates/acp-nats/src/agent/prompt.rs                                             633       0  100.00%
crates/acp-nats/src/agent/new_session.rs                                         91       0  100.00%
crates/acp-nats/src/agent/initialize.rs                                          83       0  100.00%
crates/acp-nats/src/agent/authenticate.rs                                        52       0  100.00%
crates/acp-nats/src/agent/list_sessions.rs                                       50       0  100.00%
crates/acp-nats/src/agent/logout.rs                                              49       0  100.00%
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                                              102       0  100.00%
crates/trogon-source-linear/src/config.rs                                        17       0  100.00%
crates/trogon-source-linear/src/server.rs                                       392       0  100.00%
crates/trogon-source-linear/src/signature.rs                                     54       1  98.15%   16
crates/acp-nats/src/client/session_update.rs                                     55       0  100.00%
crates/acp-nats/src/client/fs_read_text_file.rs                                 384       0  100.00%
crates/acp-nats/src/client/terminal_output.rs                                   223       0  100.00%
crates/acp-nats/src/client/rpc_reply.rs                                          71       0  100.00%
crates/acp-nats/src/client/terminal_wait_for_exit.rs                            396       0  100.00%
crates/acp-nats/src/client/mod.rs                                              2987       0  100.00%
crates/acp-nats/src/client/ext_session_prompt_response.rs                       157       0  100.00%
crates/acp-nats/src/client/terminal_release.rs                                  357       0  100.00%
crates/acp-nats/src/client/fs_write_text_file.rs                                451       0  100.00%
crates/acp-nats/src/client/request_permission.rs                                338       0  100.00%
crates/acp-nats/src/client/terminal_create.rs                                   294       0  100.00%
crates/acp-nats/src/client/terminal_kill.rs                                     309       0  100.00%
crates/acp-nats/src/client/ext.rs                                               365       8  97.81%   193-204, 229-240
crates/acp-nats-stdio/src/main.rs                                               141      27  80.85%   64, 116-123, 129-131, 148, 179-200
crates/acp-nats-stdio/src/config.rs                                              72       0  100.00%
crates/trogon-source-discord/src/server.rs                                      645       0  100.00%
crates/trogon-source-discord/src/config.rs                                      109       0  100.00%
crates/trogon-source-discord/src/signature.rs                                   103       0  100.00%
crates/trogon-source-discord/src/gateway.rs                                     449       1  99.78%   133
crates/trogon-std/src/dirs/fixed.rs                                              84       0  100.00%
crates/trogon-std/src/dirs/system.rs                                             76       0  100.00%
crates/acp-telemetry/src/lib.rs                                                 169      32  81.07%   28-34, 56-63, 98, 103, 108, 122-137, 174, 177, 180, 186
crates/acp-telemetry/src/trace.rs                                                32       3  90.62%   23-24, 32
crates/acp-telemetry/src/metric.rs                                               35       3  91.43%   30-31, 39
crates/acp-telemetry/src/signal.rs                                                7       1  85.71%   43
crates/acp-telemetry/src/service_name.rs                                         49       0  100.00%
crates/acp-telemetry/src/log.rs                                                  71       1  98.59%   43
crates/trogon-nats/src/jetstream/claim_check.rs                                 368       0  100.00%
crates/trogon-nats/src/jetstream/stream_max_age.rs                               18       0  100.00%
crates/trogon-nats/src/jetstream/publish.rs                                      64       0  100.00%
crates/trogon-nats/src/jetstream/mocks.rs                                       551       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_session.rs                   18       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/prompt_wildcard.rs               11       0  100.00%
crates/acp-nats/src/nats/subjects/subscriptions/all_session.rs                   11       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/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/one_client.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/close.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/load.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_config_option.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/cancel.rs                             18       0  100.00%
crates/acp-nats/src/nats/subjects/commands/prompt.rs                             18       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/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/initialize.rs                            8       0  100.00%
crates/acp-nats/src/nats/subjects/global/ext.rs                                  12       0  100.00%
crates/acp-nats/src/nats/subjects/global/ext_notify.rs                           12       0  100.00%
crates/acp-nats/src/nats/subjects/global/logout.rs                                8       0  100.00%
crates/acp-nats/src/nats/subjects/global/session_list.rs                          8       0  100.00%
crates/acp-nats/src/telemetry/metrics.rs                                         65       0  100.00%
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/acp-nats/src/nats/subjects/client_ops/session_request_permission.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_release.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/terminal_wait_for_exit.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/fs_write_text_file.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/session_update.rs                   15       0  100.00%
crates/trogon-std/src/secret_string.rs                                           35       0  100.00%
crates/trogon-std/src/http.rs                                                    19       0  100.00%
crates/trogon-std/src/args.rs                                                    10       0  100.00%
crates/trogon-std/src/duration.rs                                                45       0  100.00%
crates/trogon-std/src/json.rs                                                    30       0  100.00%
crates/acp-nats/src/nats/subjects/responses/update.rs                            27       0  100.00%
crates/acp-nats/src/nats/subjects/responses/cancelled.rs                         18       0  100.00%
crates/acp-nats/src/nats/subjects/responses/prompt_response.rs                   27       0  100.00%
crates/acp-nats/src/nats/subjects/responses/response.rs                          20       0  100.00%
crates/acp-nats/src/nats/subjects/responses/ext_ready.rs                         15       0  100.00%
crates/trogon-gateway/src/main.rs                                                 4       0  100.00%
crates/trogon-gateway/src/config.rs                                            1241       0  100.00%
crates/trogon-gateway/src/http.rs                                                97       0  100.00%
crates/trogon-gateway/src/streams.rs                                             64       0  100.00%
crates/trogon-source-gitlab/src/config.rs                                        17       0  100.00%
crates/trogon-source-gitlab/src/server.rs                                       431       0  100.00%
crates/trogon-source-gitlab/src/signature.rs                                     30       0  100.00%
crates/trogon-source-incidentio/src/config.rs                                    16       0  100.00%
crates/trogon-source-incidentio/src/incidentio_signing_secret.rs                 67       0  100.00%
crates/trogon-source-incidentio/src/incidentio_event_type.rs                     67       0  100.00%
crates/trogon-source-incidentio/src/server.rs                                   365       0  100.00%
crates/trogon-source-incidentio/src/signature.rs                                455       0  100.00%
crates/acp-nats/src/jetstream/consumers.rs                                       99       0  100.00%
crates/acp-nats/src/jetstream/provision.rs                                       61       0  100.00%
crates/acp-nats/src/jetstream/ext_policy.rs                                      26       0  100.00%
crates/acp-nats/src/jetstream/streams.rs                                        194       4  97.94%   254-256, 266
crates/trogon-source-slack/src/server.rs                                        954       0  100.00%
crates/trogon-source-slack/src/signature.rs                                      80       0  100.00%
crates/trogon-source-slack/src/config.rs                                         17       0  100.00%
crates/trogon-std/src/time/system.rs                                             35       0  100.00%
crates/trogon-std/src/time/mock.rs                                              129       0  100.00%
crates/trogon-std/src/env/in_memory.rs                                           81       0  100.00%
crates/trogon-std/src/env/system.rs                                              17       0  100.00%
crates/acp-nats-ws/src/config.rs                                                 83       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/main.rs                                                  189      18  90.48%   89, 209-230, 308
crates/acp-nats-ws/src/upgrade.rs                                                57       2  96.49%   59, 90
crates/acp-nats-agent/src/connection.rs                                        1434       1  99.93%   686
crates/acp-nats/src/req_id.rs                                                    39       0  100.00%
crates/acp-nats/src/session_id.rs                                                72       0  100.00%
crates/acp-nats/src/pending_prompt_waiters.rs                                   141       0  100.00%
crates/acp-nats/src/lib.rs                                                       73       0  100.00%
crates/acp-nats/src/client_proxy.rs                                             200       0  100.00%
crates/acp-nats/src/config.rs                                                   204       0  100.00%
crates/acp-nats/src/acp_prefix.rs                                                51       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/jsonrpc.rs                                                    6       0  100.00%
crates/acp-nats/src/in_flight_slot_guard.rs                                      32       0  100.00%
crates/trogon-source-github/src/config.rs                                        17       0  100.00%
crates/trogon-source-github/src/server.rs                                       351       0  100.00%
crates/trogon-source-github/src/signature.rs                                     64       0  100.00%
TOTAL                                                                         23975     190  99.21%

Diff against main

Filename                                                            Stmts    Miss  Cover
----------------------------------------------------------------  -------  ------  --------
crates/trogon-gateway/src/config.rs                                  +267       0  +100.00%
crates/trogon-gateway/src/http.rs                                      +6       0  +100.00%
crates/trogon-gateway/src/streams.rs                                   +4       0  +100.00%
crates/trogon-source-incidentio/src/config.rs                         +16       0  +100.00%
crates/trogon-source-incidentio/src/incidentio_signing_secret.rs      +67       0  +100.00%
crates/trogon-source-incidentio/src/incidentio_event_type.rs          +67       0  +100.00%
crates/trogon-source-incidentio/src/server.rs                        +365       0  +100.00%
crates/trogon-source-incidentio/src/signature.rs                     +455       0  +100.00%
TOTAL                                                               +1247       0  +0.04%

Results for commit: c985548

Minimum allowed coverage is 95%

♻️ This comment has been updated with latest results

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 1 potential issue.

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 22dbe4c. Configure here.

Comment thread rsworkspace/crates/trogon-source-incidentio/src/server.rs
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.

🧹 Nitpick comments (2)
rsworkspace/crates/trogon-source-incidentio/src/incidentio_event_type.rs (1)

22-23: Consider implementing source() to preserve error chain.

The IncidentioEventTypeError wraps a SubjectTokenViolation but doesn't expose it via source(). While not critical since the Display impl provides good messages, implementing source() would preserve the full error chain for debugging.

♻️ Optional: implement source() for error chain
-impl std::error::Error for IncidentioEventTypeError {}
+impl std::error::Error for IncidentioEventTypeError {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        Some(&self.0)
+    }
+}

This requires SubjectTokenViolation to implement std::error::Error. Verify that it does before applying.

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

In `@rsworkspace/crates/trogon-source-incidentio/src/incidentio_event_type.rs`
around lines 22 - 23, The impl of std::error::Error for IncidentioEventTypeError
should implement source() to return the wrapped SubjectTokenViolation so the
error chain is preserved; update the impl for IncidentioEventTypeError to add a
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> that returns
Some(&self.0) (or the appropriate field holding the SubjectTokenViolation), and
before applying ensure SubjectTokenViolation itself implements std::error::Error
(if it doesn't, either implement Error for it or wrap/convert it to a type that
does).
rsworkspace/crates/trogon-source-incidentio/src/server.rs (1)

51-71: Consider adding webhook metadata headers to unroutable messages.

Unlike routable messages (lines 193-203), unroutable messages don't include webhook_id or webhook_timestamp headers. This metadata could be useful for debugging rejected webhooks. However, since unroutable is called before verified is available in some code paths (invalid JSON), this would require restructuring.

This is a minor observability improvement and not blocking.

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

In `@rsworkspace/crates/trogon-source-incidentio/src/server.rs` around lines 51 -
71, publish_unroutable currently sets only NATS_HEADER_REJECT_REASON and omits
webhook metadata; update the signature of publish_unroutable (the async fn
publish_unroutable<P: JetStreamPublisher, S: ObjectStorePut>) to accept optional
webhook metadata parameters (e.g. webhook_id: Option<&str> and
webhook_timestamp: Option<&str> or Option<impl Into<String>>), then when
building async_nats::HeaderMap insert NATS_HEADER_REJECT_REASON as before and
also insert headers for "webhook_id" and "webhook_timestamp" only when their
Option is Some; finally update all call sites to pass actual webhook values when
available (or None for invalid-JSON/unverified paths) so unroutable messages
include metadata when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@rsworkspace/crates/trogon-source-incidentio/src/incidentio_event_type.rs`:
- Around line 22-23: The impl of std::error::Error for IncidentioEventTypeError
should implement source() to return the wrapped SubjectTokenViolation so the
error chain is preserved; update the impl for IncidentioEventTypeError to add a
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> that returns
Some(&self.0) (or the appropriate field holding the SubjectTokenViolation), and
before applying ensure SubjectTokenViolation itself implements std::error::Error
(if it doesn't, either implement Error for it or wrap/convert it to a type that
does).

In `@rsworkspace/crates/trogon-source-incidentio/src/server.rs`:
- Around line 51-71: publish_unroutable currently sets only
NATS_HEADER_REJECT_REASON and omits webhook metadata; update the signature of
publish_unroutable (the async fn publish_unroutable<P: JetStreamPublisher, S:
ObjectStorePut>) to accept optional webhook metadata parameters (e.g.
webhook_id: Option<&str> and webhook_timestamp: Option<&str> or Option<impl
Into<String>>), then when building async_nats::HeaderMap insert
NATS_HEADER_REJECT_REASON as before and also insert headers for "webhook_id" and
"webhook_timestamp" only when their Option is Some; finally update all call
sites to pass actual webhook values when available (or None for
invalid-JSON/unverified paths) so unroutable messages include metadata when
present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63f8a2af-b635-4971-88fb-f1d09d0a4670

📥 Commits

Reviewing files that changed from the base of the PR and between c05ebcb and 22dbe4c.

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

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: 2

🧹 Nitpick comments (1)
rsworkspace/crates/trogon-gateway/src/config.rs (1)

186-209: Prefer typed value objects at the Incident.io config boundary.

This section introduces raw String/u64 primitives for domain fields, which allows invalid states before resolution. Consider shifting these to domain-specific value objects (or typed wrappers) as early as possible in construction.

As per coding guidelines, Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String). Each type's factory must guarantee 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-gateway/src/config.rs` around lines 186 - 209, The
IncidentioConfig uses raw primitives (String/u64) for domain fields which
permits invalid states; replace those primitives with domain-specific value
objects (e.g., SubjectPrefix, StreamName, SigningSecretOption wrapper,
StreamMaxAgeSecs, NatsAckTimeoutSecs, TimestampToleranceSecs) that enforce
validation at construction and make invalid states unrepresentable. Update the
IncidentioConfig definition to use these types for the fields signing_secret,
subject_prefix, stream_name, stream_max_age_secs, nats_ack_timeout_secs, and
timestamp_tolerance_secs, implement fallible factories (or FromStr/Deserialize)
on each value object to validate env input when the config is built, and add
conversion/mapper code where the config is consumed so callers receive
already-validated domain types. Ensure each type's factory returns a Result or
implements TryFrom/FromStr with clear errors and wire those into your config
loading path so invalid env values fail fast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rsworkspace/crates/trogon-gateway/src/config.rs`:
- Line 1072: The test data currently inlines literal-looking secrets like
"whsec_dGVzdC1zZWNyZXQ=" causing scanners to flag them; update tests to generate
the valid test secret at runtime via a helper (e.g., add a function
build_test_whsec() and call it from incidentio_toml or the call site) and
replace all direct uses (references: incidentio_toml and write_toml) with calls
that use that helper; apply the same replacement for the other occurrences
mentioned (lines around the other incidentio_toml/write_toml calls) so no
literal-looking secrets remain in the source.
- Around line 718-759: The code is converting typed parse errors into Strings
(e.g., in the NatsToken::new, NonZeroDuration::from_secs and
StreamMaxAge::from_secs branches) which loses error context; define a typed
validation error enum/struct (e.g., ConfigValidationError with variants like
InvalidSigningSecret { source: E }, InvalidSubjectPrefix { source: E },
InvalidStreamName { source: E }, ZeroDuration { field: &'static str } etc.),
replace the errors: Vec<String> with errors: Vec<ConfigValidationError>, and
when a parse fails push the appropriate ConfigValidationError variant carrying
the original source error instead of format!() and return/propagate that typed
error (or collect and return a Result/Err containing the typed errors) from the
resolver.

---

Nitpick comments:
In `@rsworkspace/crates/trogon-gateway/src/config.rs`:
- Around line 186-209: The IncidentioConfig uses raw primitives (String/u64) for
domain fields which permits invalid states; replace those primitives with
domain-specific value objects (e.g., SubjectPrefix, StreamName,
SigningSecretOption wrapper, StreamMaxAgeSecs, NatsAckTimeoutSecs,
TimestampToleranceSecs) that enforce validation at construction and make invalid
states unrepresentable. Update the IncidentioConfig definition to use these
types for the fields signing_secret, subject_prefix, stream_name,
stream_max_age_secs, nats_ack_timeout_secs, and timestamp_tolerance_secs,
implement fallible factories (or FromStr/Deserialize) on each value object to
validate env input when the config is built, and add conversion/mapper code
where the config is consumed so callers receive already-validated domain types.
Ensure each type's factory returns a Result or implements TryFrom/FromStr with
clear errors and wire those into your config loading path so invalid env values
fail fast.
🪄 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: e993f6d2-6b1a-4cdf-92c1-5d5a30b9246a

📥 Commits

Reviewing files that changed from the base of the PR and between 22dbe4c and fade5df.

📒 Files selected for processing (6)
  • devops/docker/compose/.env.example
  • devops/docker/compose/services/trogon-gateway/README.md
  • rsworkspace/crates/trogon-gateway/src/config.rs
  • rsworkspace/crates/trogon-source-incidentio/src/incidentio_signing_secret.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs
  • rsworkspace/crates/trogon-source-incidentio/src/signature.rs
✅ Files skipped from review due to trivial changes (3)
  • devops/docker/compose/services/trogon-gateway/README.md
  • devops/docker/compose/.env.example
  • rsworkspace/crates/trogon-source-incidentio/src/signature.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • rsworkspace/crates/trogon-source-incidentio/src/incidentio_signing_secret.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs

Comment thread rsworkspace/crates/trogon-gateway/src/config.rs Outdated
Comment thread rsworkspace/crates/trogon-gateway/src/config.rs Outdated
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: 1

🧹 Nitpick comments (1)
rsworkspace/crates/trogon-source-incidentio/src/signature.rs (1)

20-21: Preserve missing-header context instead of collapsing to MissingHeaders.

At Line 96, missing required headers are collapsed into a single MissingHeaders error. Include the header name to improve diagnostics and downstream handling.

Proposed fix
 pub enum SignatureError {
-    MissingHeaders,
+    MissingHeader { name: &'static str },
@@
-            Self::MissingHeaders => f.write_str("missing required signature headers"),
+            Self::MissingHeader { name } => write!(f, "missing required header {name}"),
@@
-        .ok_or(SignatureError::MissingHeaders)?
+        .ok_or(SignatureError::MissingHeader { name })?

Also applies to: 38-41, 93-99

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

In `@rsworkspace/crates/trogon-source-incidentio/src/signature.rs` around lines 20
- 21, The enum currently collapses missing-required-header errors into the unit
variant MissingHeaders; change the MissingHeaders variant to carry the header
name (e.g., MissingHeaders(String) or &'static str) so the missing-header
context is preserved, then update every place that constructs MissingHeaders
(look for code in signature.rs that builds MissingHeaders around header
parsing/validation) to pass the header name string, update any pattern matches
on MissingHeaders to destructure the contained name, and run/adjust tests and
From/Display/impl Error conversions that reference MissingHeaders to include the
new payload; leave InvalidHeaderValue as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rsworkspace/crates/trogon-source-incidentio/src/signature.rs`:
- Around line 62-66: Replace the primitive String fields in VerifiedWebhook
(webhook_id, webhook_timestamp) with domain value objects (e.g., WebhookId,
WebhookTimestamp) that enforce validation at construction; create small newtypes
or structs with validated constructors (new/from_str/try_from) that return
Result and guarantee only valid instances can be created, update the
VerifiedWebhook definition to use these types, and update all places that
construct or serialize/deserialize VerifiedWebhook (the verification/parsing
code that previously populated webhook_id and webhook_timestamp) to construct
the value objects and propagate errors; apply the same pattern to the other
similar struct(s) referenced in the review (the fields at the other occurrence)
so primitives are replaced by validated domain value objects.

---

Nitpick comments:
In `@rsworkspace/crates/trogon-source-incidentio/src/signature.rs`:
- Around line 20-21: The enum currently collapses missing-required-header errors
into the unit variant MissingHeaders; change the MissingHeaders variant to carry
the header name (e.g., MissingHeaders(String) or &'static str) so the
missing-header context is preserved, then update every place that constructs
MissingHeaders (look for code in signature.rs that builds MissingHeaders around
header parsing/validation) to pass the header name string, update any pattern
matches on MissingHeaders to destructure the contained name, and run/adjust
tests and From/Display/impl Error conversions that reference MissingHeaders to
include the new payload; leave InvalidHeaderValue as-is.
🪄 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: 385c6c98-1069-474a-8d60-24fd7da3ae57

📥 Commits

Reviewing files that changed from the base of the PR and between fade5df and 165ffc7.

📒 Files selected for processing (3)
  • rsworkspace/crates/trogon-source-incidentio/src/constants.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs
  • rsworkspace/crates/trogon-source-incidentio/src/signature.rs
✅ Files skipped from review due to trivial changes (2)
  • rsworkspace/crates/trogon-source-incidentio/src/constants.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs

Comment thread rsworkspace/crates/trogon-source-incidentio/src/signature.rs
@yordis yordis force-pushed the yordis/incidentio-gateway branch from 1ca84fa to 1c8ce42 Compare April 11, 2026 03:28
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: 1

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

Inline comments:
In `@rsworkspace/crates/trogon-source-incidentio/src/server.rs`:
- Around line 96-101: The JetStream stream is created with the default
duplicate_window (2m) but signature validation uses timestamp_tolerance (5m), so
set the stream's duplicate_window to match or exceed the configured
timestamp_tolerance to prevent replayed webhooks from bypassing dedupe; update
the async_nats::jetstream::stream::Config passed to js.get_or_create_stream
(alongside name/subjects/max_age) to include duplicate_window =
Duration::from_secs(config.timestamp_tolerance) or otherwise convert
config.timestamp_tolerance into the same Duration type so duplicate_window >=
timestamp_tolerance.
🪄 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: 3639c1c3-2088-4d15-95e2-de80eeba23e4

📥 Commits

Reviewing files that changed from the base of the PR and between 165ffc7 and 1ca84fa.

📒 Files selected for processing (5)
  • rsworkspace/crates/trogon-gateway/src/config.rs
  • rsworkspace/crates/trogon-source-incidentio/src/config.rs
  • rsworkspace/crates/trogon-source-incidentio/src/incidentio_signing_secret.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs
  • rsworkspace/crates/trogon-source-incidentio/src/signature.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rsworkspace/crates/trogon-source-incidentio/src/config.rs

Comment thread rsworkspace/crates/trogon-source-incidentio/src/server.rs
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.

🧹 Nitpick comments (1)
rsworkspace/crates/trogon-gateway/src/streams.rs (1)

112-112: Consider asserting expected stream identities, not only total count.

A count-only check can miss wrong stream selection/order regressions; asserting presence of expected stream names would make this test more resilient.

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

In `@rsworkspace/crates/trogon-gateway/src/streams.rs` at line 112, The test
currently only checks count via assert_eq!(js.created_streams().len(), 7) which
misses wrong selections/order; update the assertion to verify the actual stream
identities by mapping js.created_streams() to their identifying field (e.g.,
name or id) and assert that the resulting collection matches the expected
list/set of stream names/ids (use assert_eq! for an exact set or
assert!(contains) checks for presence), referencing created_streams() and the
existing assert_eq! call so you replace the count-only check with a comparison
against the expected identities.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@rsworkspace/crates/trogon-gateway/src/streams.rs`:
- Line 112: The test currently only checks count via
assert_eq!(js.created_streams().len(), 7) which misses wrong selections/order;
update the assertion to verify the actual stream identities by mapping
js.created_streams() to their identifying field (e.g., name or id) and assert
that the resulting collection matches the expected list/set of stream names/ids
(use assert_eq! for an exact set or assert!(contains) checks for presence),
referencing created_streams() and the existing assert_eq! call so you replace
the count-only check with a comparison against the expected identities.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df3d894e-f1f6-4376-b090-bd325caae9b7

📥 Commits

Reviewing files that changed from the base of the PR and between 1ca84fa and 1c8ce42.

⛔ Files ignored due to path filters (1)
  • rsworkspace/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • devops/docker/compose/.env.example
  • devops/docker/compose/compose.yml
  • devops/docker/compose/services/trogon-gateway/README.md
  • rsworkspace/Cargo.toml
  • rsworkspace/crates/trogon-gateway/Cargo.toml
  • rsworkspace/crates/trogon-gateway/src/config.rs
  • rsworkspace/crates/trogon-gateway/src/http.rs
  • rsworkspace/crates/trogon-gateway/src/streams.rs
  • rsworkspace/crates/trogon-source-incidentio/Cargo.toml
  • rsworkspace/crates/trogon-source-incidentio/src/config.rs
  • rsworkspace/crates/trogon-source-incidentio/src/constants.rs
  • rsworkspace/crates/trogon-source-incidentio/src/incidentio_event_type.rs
  • rsworkspace/crates/trogon-source-incidentio/src/incidentio_signing_secret.rs
  • rsworkspace/crates/trogon-source-incidentio/src/lib.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs
  • rsworkspace/crates/trogon-source-incidentio/src/signature.rs
✅ Files skipped from review due to trivial changes (5)
  • rsworkspace/crates/trogon-gateway/Cargo.toml
  • rsworkspace/Cargo.toml
  • devops/docker/compose/.env.example
  • rsworkspace/crates/trogon-source-incidentio/src/lib.rs
  • rsworkspace/crates/trogon-source-incidentio/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (6)
  • rsworkspace/crates/trogon-source-incidentio/src/config.rs
  • devops/docker/compose/compose.yml
  • devops/docker/compose/services/trogon-gateway/README.md
  • rsworkspace/crates/trogon-source-incidentio/src/incidentio_signing_secret.rs
  • rsworkspace/crates/trogon-gateway/src/config.rs
  • rsworkspace/crates/trogon-source-incidentio/src/server.rs

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the yordis/incidentio-gateway branch from 735437e to c985548 Compare April 11, 2026 03:54
@yordis yordis merged commit 6ccd649 into main Apr 11, 2026
7 checks passed
@yordis yordis deleted the yordis/incidentio-gateway branch April 11, 2026 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant