# Sentry SDK compatibility

Bucker's ingest speaks Sentry's envelope protocol, so an unmodified Sentry SDK pointed at a
Bucker DSN reports here. Changing the DSN is the only change. This page says exactly what of
that traffic is kept, what is refused, and why. The wire grammar itself is in
[the ingest protocol](ingest-protocol.md).

Nothing on this page is a belief. The matrix below is the output of a conformance suite that
runs the real, pinned SDKs against a local Bucker api and worker and reads back what was
stored (see [How the matrix is made](#how-the-matrix-is-made)).

## Certified matrix

`kept` means stored and readable in the product. `refused` means refused **by name** when
it arrived, with the outcome reason shown, not billed, and shown on the drops panel with its
own explanation; the rest of the envelope is processed. `—` means that SDK version did not
send it in the scenario, either because it has no API for it or because it does not send it
by default.

<!-- certified-matrix:start -->
| SDK | Version | Run | Errors | Traces | Logs | Sessions (per session) | Sessions (server aggregates) | Cron check-ins | User feedback | Attachments (text) | Session Replay |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `sentry.javascript.browser` | 7.120.4 | live | kept | — | — | kept | — | — | kept | kept | refused (`sentry_rrweb_replay`) |
| `sentry.javascript.browser` | 8.55.2 | live | kept | kept | — | kept | — | — | kept | kept | refused (`sentry_rrweb_replay`) |
| `sentry.javascript.browser` | 10.76.2 | live | kept | kept | kept | kept | — | — | kept | kept | refused (`sentry_rrweb_replay`) |
| `sentry.javascript.browser` | 11.8.0 | live | kept | kept | kept | kept | — | — | kept | kept | refused (`sentry_rrweb_replay`) |
| `sentry.javascript.node` | 7.120.4 | live | kept | kept | — | kept | — | kept | — | kept | — |
| `sentry.javascript.node` | 8.55.2 | live | kept | kept | — | kept | kept | kept | kept | kept | — |
| `sentry.javascript.node` | 10.76.2 | live | kept | kept | kept | kept | kept | kept | kept | kept | — |
| `sentry.javascript.node` | 11.8.0 | live | kept | kept | kept | kept | kept | kept | kept | kept | — |
| `sentry.python` | 2.72.0 | live | kept | kept | kept | — | kept | kept | — | — | — |
<!-- certified-matrix:end -->

Python's row is `recorded` when the suite ran without an interpreter that can import
`sentry-sdk`: the bytes that `sentry-sdk`'s own serializer produced are replayed through the
same route, gzipped and authenticated as its HTTP transport sends them.

## What carries over

| Feature | What Bucker does with it |
| --- | --- |
| Errors (`event`) | Grouped into issues, scrubbed, symbolicated by Debug ID. |
| Traces (`transaction`, and Sentry JS v11's streamed `span` batches) | Stored as spans in the same table OTLP spans land in. The root span is named after the transaction, children after their description; `op`, tags and the transaction name are kept as attributes. Metered as one `transaction` unit per span, as OTLP meters the same rows. |
| Logs (`log`) | Stored as log records, with `level`, typed attributes, environment and release read from Sentry's record shape. |
| Sessions, per session (`session`) | Release health: crash-free sessions and crash-free users. A session with no `release` is refused (`session_missing_release`). |
| Sessions, server aggregates (`sessions`) | The per-minute counts a server SDK sends in request mode. They feed crash-free **sessions** and adoption. They cannot feed crash-free **users**: an aggregate has no per-session identity. The dashboard says "not derivable from aggregates" rather than computing a number, and the API reports how many sessions arrived this way (`aggregatedSessions`). |
| Cron check-ins (`check_in`) | Recorded on the cron monitor named by `monitor_slug`. A monitor is created from `monitor_config` when it does not exist yet (crontab, or a minute, hour, day or week interval), under the same rules as the HTTP check-in route. |
| User feedback (`feedback`, `user_report`) | Stored as feedback, linked to the issue of the event it names, with the same spam scoring and injection scanning as the feedback widget's route. |
| Client reports (`client_report`) | The SDK's own drop counts, on the drops panel. |
| Attachments (`attachment`) | Stored and shown on the event's Attachments panel — see [Attachments](#attachments) below for what is kept. Metered per started 256 KiB. |
| Source-map uploads (`sentry-cli sourcemaps upload`, the `sentry` CLI, `@sentry/*-plugin`) | Served: chunk-upload, artifact-bundle assemble, releases new/finalize and deploys, with `SENTRY_URL` at the Bucker api and a Bucker org CI token or PAT as the auth token. Each map is stored under its Debug ID and verified; a bundle with no Debug IDs fails the upload. See [Switch from Sentry §3](switch-from-sentry.md#3-source-maps). |

## What does not carry over

| Feature | What happens, and why |
| --- | --- |
| Session Replay (rrweb `replay_event` + `replay_recording`) | Refused by name as `unsupported_item_type:sentry_rrweb_replay`. Bucker's player reads its own interaction stream, not rrweb DOM snapshots. Use the Bucker browser SDK for replay, or turn replay off in the Sentry SDK. |
| Binary attachments other than minidumps, credential-shaped files, and screenshots on a project that did not opt in | Refused by name, not billed — see [Attachments](#attachments). |
| Profiles (`profile`, `profile_chunk`) | Refused by name as `unsupported_item_type:profile`. Bucker does not offer profiling; set the profile sample rate to zero. |
| Crash-free users from server aggregates | Not derivable, as above. |
| Legacy release-file uploads (`sentry-cli releases files upload`, release-plus-filename matching) | Not served. Debug-ID artifact bundles only — the upload path above. |

## Which SDKs are talking to a project

**Settings › Projects › SDKs** lists every SDK that reported to a project in the last week —
name and version from the envelope header, Bucker's or Sentry's, its share of the envelopes,
what it sent by kind (events, span items, logs, sessions, check-ins) and how many of its items
were dropped at the edge, with a link to the drops panel narrowed to that project. Each row
carries the hints that apply to it:

| Hint | When |
| --- | --- |
| Below the supported version | A Bucker SDK older than its floor (the same floor the ingest `Deprecation` header announces). |
| Not certified | A Sentry SDK major the [certified matrix](#certified-matrix) does not include, or a language the suite does not run (Go, Ruby, PHP, Java, .NET). |
| Streams spans | Sentry JS 11's span batches, which are stored as spans. |
| Sends only traces | Spans but no errors: issues appear once the SDK captures one. |
| Items refused | Something it sent was refused by name (replays, profiles) or was over the item size. |

`GET /orgs/{orgSlug}/projects/{projectSlug}/sdks` is the same list for a script. The counts are
gathered without a database write per envelope, so they are approximate to within a few seconds.

## Attachments

`attachment` items (`Sentry.addAttachment`, `scope.addAttachment`, and Bucker's own SDKs) are
stored against the event they name — the envelope header's `event_id`, or the item header's —
in object storage, and listed on that event's **Attachments** panel with a download link and,
for text, an inline view.

| Kind | Kept? | How |
| --- | --- | --- |
| Text and logs (`text/*`, JSON, XML, YAML, and `.log`/`.txt`/`.har`… names) | Yes | **Scrubbed before it is written**, line by line, with the same built-in rules and your organization's own scrub rules the event goes through. The panel says how many values were redacted. |
| Minidumps (`attachment_type: event.minidump`, `.dmp`) | Yes | Stored as sent: a minidump is binary and cannot be scrubbed. Bucker does not process it (no native symbolication); it is there to download. |
| Screenshots (`image/png`, `image/jpeg`, …) | Only if the project opts in | Off by default (Settings › Projects › Attachments), following [the replay posture](replay-privacy.md): a screenshot is a picture of whatever was on a user's screen. Masking is the capturing SDK's job. Refused as `unsupported_item_type:attachment_screenshot` otherwise. |
| Any other binary | No | Refused as `unsupported_item_type:attachment_binary`: nothing renders it and nothing can scrub it. |
| A file named like a credential (`.env`, `*.pem`, `*.key`, `id_rsa`, `credentials.json`, …) | No | Refused unread as `unsupported_item_type:attachment_secret_name`, whatever its type. |

Refusals happen at the accept edge and are not billed. A kept attachment is metered as one
`attachment` unit per started 256 KiB (an item is at most 1 MiB). An event keeps at most 20
attachments (5 MiB in all), and an organization at most its plan's standing attachment storage
(100 MiB on Free, 5 GiB on Pro, 20 GiB on Team, 100 GiB on Enterprise); past either, the
attachment is not stored and the drops panel says which limit it hit. Attachments are deleted
with the plan's event retention, object first.

Downloads are five-minute signed links, minted for a reader who can see the project, and served
as a file download that the browser will not render.

## Mobile

Bucker does not offer native symbolication. Nothing on the server resolves a dSYM, a ProGuard
mapping or a Dart symbol file, so a native frame from an iOS, Android or obfuscated Flutter
build arrives unreadable however its symbols are uploaded.

That is why the artifact upload route refuses `DSYM`, `PROGUARD` and `DART_SYMBOLS` with a
`422` (`reason: native-symbolication-not-offered`) instead of storing and billing bytes that
could never resolve a frame. Source maps (`SOURCEMAP`) and bundles are unaffected.

What this means for a Sentry mobile SDK pointed at Bucker: errors are stored and grouped, but
their native frames stay unsymbolicated. Treat the mobile SDKs as "errors only, no native
symbolication". They are not in the certified matrix.

An operator who runs a native symbolication service of their own reopens the upload route
with `SYMBOLICATION_NATIVE_UPLOADS=true`. It is a single switch so that reopening it is a
configuration change rather than a code change.

## How the matrix is made

`code/integration-tests/test/sentry-compat/` runs each pinned SDK (`@sentry/node` and
`@sentry/browser` 7, 8, 10 and 11, installed under npm aliases as devDependencies of
`@bucker/integration-tests`, and `sentry-sdk` for Python) in a child process of its own,
over the SDK's own transport, against the DSN of a project signed up through the real routes
on a listening api. The browser SDK runs inside jsdom. Each SDK sends the same scenario: an
error, a trace, a log, a cron check-in pair, user feedback, its sessions, and (browser) a
Session Replay. The worker then drains the queue and the suite reads the rows back.

Two things are asserted for every SDK. First, the observed matrix equals the committed
`certified-matrix.json`, cell for cell, so this page can never claim more than a run shows.
Second, nothing is dropped: every `invalid` outcome a run leaves must be one of the by-name
refusals above. A Sentry payload reshaped in a new release fails the suite instead of
becoming a silent drop. The suite needs no network and runs under `make -C code test`.

Python runs live when an interpreter that can import `sentry_sdk` is available
(`BUCKER_SENTRY_PYTHON` names one; otherwise `python3` is tried), and from the recorded
fixture otherwise. Both paths must certify the same row.

**Re-pin monthly:**

1. Bump the aliases in `code/integration-tests/package.json` and run `make -C code install`.
   Never install through `scripts/secrets.sh`: under `exec prod` the install inherits
   `NODE_ENV=production` and omits devDependencies.
2. For Python, re-record the fixture with an interpreter that has the new `sentry-sdk`:
   `python3 code/integration-tests/test/sentry-compat/emitters/sentry_python.py http://k@127.0.0.1:9/1 --dump code/integration-tests/test/sentry-compat/fixtures/sentry-python-<version>.json`,
   and delete the old one.
3. Regenerate the matrix and this page's table from a run:
   `SENTRY_COMPAT_RECORD=1 pnpm -C code --filter @bucker/integration-tests exec vitest run test/sentry-compat`.
4. Review the diff. A cell that moved from `kept` is a regression to fix, not a matrix to
   accept.
