recoverActiveMissions (mission-execution-loop.ts:263) calls missionStore.reconcileSupersededGeneratedFixFeatures per slice; the 5 MissionExecutionLoop-backed mocks here omitted it, so recovery threw (TypeError) at the slice loop and aborted before processTaskOutcome / ensureFeatureAssertionLinked / startValidatorRun ran — 4 tests failed. Add a no-op stub (matches mission-execution-loop.test.ts reference) with an FNXC:MissionReconcile note. No-op is correct: supersession is not exercised by these tests.
9.6 KiB
Signals Connectors
Fusion can receive signed external signals from GitLab, Sentry, Datadog, PagerDuty, or a generic webhook at:
POST /api/signals/:provider
Supported providers are webhook, gitlab, sentry, datadog, and pagerduty. Every connector requires a verification secret configured in the Fusion dashboard process environment. Generic webhook, Sentry, Datadog, and PagerDuty use HMAC signatures; GitLab uses GitLab's X-Gitlab-Token secret-token header. Verified signals still create triage tasks, and they also write to the project-scoped incidents table so Command Center → Signals can show source, severity, and open/resolved status breakdowns.
Runtime behavior
- Open events create or absorb an incident occurrence by
groupingKeyand preserve the normalizedsource,severity, optionallink, and cappedmetafields. - Resolved events write/absorb the incident and then mark the matching
groupingKeyasresolved. Cold resolves are retained as resolved signal metrics instead of being dropped. - Duplicate deliveries with the same provider external id are accepted as deduped and do not create a second task or incident write.
- Re-fired incidents with the same
groupingKeyare absorbed by the incidents store rather than double-counted as separate incidents. - The connectors status endpoint,
GET /api/command-center/signals/connectors, returns only{ provider, configured }booleans. It never returns secret values.
Security model
- Secrets are environment variables; do not commit them to source control.
- HMAC verification uses the raw request body and constant-time comparison where the provider supplies an HMAC signature. GitLab secret-token verification compares
X-Gitlab-TokentoFUSION_SIGNAL_GITLAB_SECRETwith constant-time comparison. - Requests are capped at about 1 MB.
- Replay protection rejects stale timestamps where the provider supplies one and rejects repeated delivery ids within the replay window.
- Normalized
title,body,groupingKey,link, andmetafields are capped bysignal-source.tsbefore storage. - Signal
linkvalues are SSRF-untrusted. Fusion stores safe external URLs as data for the UI and never fetches connector links server-side. metais stored as JSON data only and must not be rendered as raw HTML.
Generic webhook
Set:
export FUSION_SIGNAL_WEBHOOK_SECRET="replace-with-a-long-random-secret"
Headers:
X-Fusion-Signature:sha256=-prefixed HMAC-SHA256 hex digest of the raw JSON body.X-Fusion-Timestamp: epoch milliseconds, used for the replay window.
Payload contract:
{
"id": "delivery-123",
"title": "API error rate above threshold",
"body": "5xx rate exceeded 10% for 5 minutes",
"severity": "critical",
"groupingKey": "api-error-rate",
"link": "https://example.com/incidents/api-error-rate",
"timestamp": 1790294400000,
"status": "open",
"meta": { "service": "api" }
}
Resolution mapping: status: "resolved", action: "resolved", or action: "resolve" resolves the grouped incident. Any other value opens/absorbs the incident.
Example signed request:
body='{"id":"delivery-123","title":"API error rate above threshold","severity":"critical","groupingKey":"api-error-rate","timestamp":'"$(date +%s000)"'}'
sig="sha256=$(printf '%s' "$body" | openssl dgst -sha256 -hmac "$FUSION_SIGNAL_WEBHOOK_SECRET" -hex | awk '{print $2}')"
curl -X POST "http://127.0.0.1:4040/api/signals/webhook" \
-H "Content-Type: application/json" \
-H "X-Fusion-Timestamp: $(date +%s000)" \
-H "X-Fusion-Signature: $sig" \
--data-binary "$body"
Sentry
Set:
export FUSION_SIGNAL_SENTRY_SECRET="sentry-integration-client-secret"
Configure the Sentry integration webhook target as /api/signals/sentry. Fusion verifies Sentry-Hook-Signature as the HMAC-SHA256 hex digest of the raw body. If Sentry sends Sentry-Hook-Timestamp, Fusion checks it against the replay window.
Normalization:
groupingKey: Sentryissue.id.source:sentry.severity:fatal/critical→critical,error→error,warning→warning,info/debug→info.- Resolution: payload
action === "resolved"orissue.status === "resolved"resolves the grouped issue; other actions open/absorb it.
Datadog
Set:
export FUSION_SIGNAL_DATADOG_SECRET="datadog-webhook-shared-secret"
Datadog webhooks do not provide a built-in HMAC header, so configure a custom header:
X-Datadog-Signature: <HMAC-SHA256 hex digest of the raw body>
Optionally include X-Datadog-Timestamp as epoch milliseconds for replay-window validation. Configure the Datadog webhook target as /api/signals/datadog.
Normalization:
groupingKey:aggreg_key,alert_id, orid.source:datadog.severity:error→critical,warning/warn→warning,success/recovery/info→info, default →error.- Resolution:
alert_typeofrecoveryorsuccessresolves the grouped monitor; other alert types open/absorb it.
PagerDuty
Set:
export FUSION_SIGNAL_PAGERDUTY_SECRET="pagerduty-webhook-subscription-secret"
Configure the PagerDuty webhook subscription target as /api/signals/pagerduty. Fusion verifies X-PagerDuty-Signature and accepts the v1=<hex> signature form.
Normalization:
groupingKey: PagerDuty incidentdata.id.source:pagerduty.severity: explicitdata.severitywhen it is one of Fusion's normalized severities; otherwise high urgency maps tocriticaland other events map towarning.- Resolution:
event.event_type === "incident.resolved"ordata.status === "resolved"resolves the grouped incident; other incident events open/absorb it.
GitLab
Set:
export FUSION_SIGNAL_GITLAB_SECRET="gitlab-webhook-secret-token"
Configure a GitLab project or group webhook with:
- URL:
https://<your-fusion-host>/api/signals/gitlab - Secret token: the same value as
FUSION_SIGNAL_GITLAB_SECRET - Triggers: Issue events and/or Merge request events
Fusion verifies GitLab's X-Gitlab-Token header. GitLab's webhook docs now recommend signing tokens for new webhooks, but this connector intentionally supports the documented secret-token compatibility path required by existing GitLab.com and self-managed GitLab installations. This task introduces no GitLab binary, CLI, download, or checksum-managed artifact.
Broader GitHub-to-GitLab parity (issue import, linked tracking, lifecycle automation, and Command Center analytics) is mapped in GitLab Parity Inventory. These signal webhooks are the GitLab side of that parity surface.
Supported GitLab events:
- Project and group Issue Hook payloads with
object_kind/event_typeofissue. - Project and group Merge Request Hook payloads with
object_kind/event_typeofmerge_request. - Work item issue payloads (
object_kind: "work_item") are treated as issue signals when they provide issue-shapedobject_attributes.
Normalization:
source:gitlab.groupingKey:gitlab:<project-or-group>:<issue|merge_request>:<iid>usingproject.path_with_namespace, project id, group full path, or group id when available.externalId: GitLab delivery headers such asX-Gitlab-Event-UUID,Idempotency-Key,webhook-id, orX-Request-Idwhen present; otherwise a stable fallback that includes grouping key, action, state, and timestamp so open and recovery events for the same IID are not deduped together.title:GitLab issue #<iid>: <title>orGitLab merge request !<iid>: <title>.link:object_attributes.urlwhen safe, otherwise a project URL fallback. Fusion supports both GitLab.com and self-managed instance URLs and never fetches these links server-side.severity: GitLab issue severity values such ascritical,high,medium, andlowmap to Fusion's normalized severities; merge requests default toinfo.- Resolution: issue
action/stateofclose/closedresolves the grouped signal; merge requestmerge/mergedorclose/closedresolves it;open,update, andreopenopen/absorb it.
Non-actionable or unsupported GitLab events, including push/test/ping/system-hook-style payloads, are accepted as non-actionable and do not create tasks. Malformed actionable issue/MR payloads, such as missing object_attributes.iid or object_attributes.title, return a 4xx response and do not create tasks.
Relevant upstream evidence:
- GitLab upstream project: https://gitlab.com/gitlab-org/gitlab
- GitLab webhook docs: https://docs.gitlab.com/user/project/integrations/webhooks/
- GitLab webhook event reference: https://docs.gitlab.com/user/project/integrations/webhook_events/
- GitLab group webhooks: https://docs.gitlab.com/user/group/webhooks/
- GitLab releases: https://gitlab.com/gitlab-org/gitlab/-/releases
Command Center Signals
Command Center → Signals reads aggregated incidents through GET /api/command-center/signals. Once a connector secret is configured and signed events arrive, the area displays total, open, resolved, MTTR, by-source, by-severity, and by-status metrics from local incident rows.
The empty state is intentionally explicit:
- no configured connector secret: prompt operators to connect GitLab, Sentry, Datadog, PagerDuty, or the generic webhook;
- at least one configured connector secret but no rows in the selected range: report that Fusion is configured and awaiting signals.
Separate monitor ingest path
FUSION_MONITOR_INGEST_SECRET protects the separate bearer-token route for /api/monitor/incidents. It is not used by /api/signals/:provider; signal connectors use the provider-specific FUSION_SIGNAL_*_SECRET variables above.