Skip to content

chore: INFRA-295 correct Sentry release-health session option + crash attribution - #218

Merged
MP2EZ merged 1 commit into
developmentfrom
chore/INFRA-295-sentry-release-health-sessions
Jul 25, 2026
Merged

chore: INFRA-295 correct Sentry release-health session option + crash attribution#218
MP2EZ merged 1 commit into
developmentfrom
chore/INFRA-295-sentry-release-health-sessions

Conversation

@MP2EZ

@MP2EZ MP2EZ commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Closes INFRA-295

Re-scoped: a planning panel falsified the story's premise. The item assumed release-health session tracking was deliberately disabled and needed enabling. It was not disabled.

1. The option key was dead

ExternalErrorReporter passed autoSessionTracking: false, but @sentry/react-native reads enableAutoSessionTracking. The key we set does not exist in the SDK, is forwarded verbatim to the native layer, and is ignored there β€” and both native SDKs default session tracking ON.

enableAutoSessionTracking  β†’ 4 files in @sentry/react-native/dist/js
autoSessionTracking (bare) β†’ 0 files

So the "privacy-first, error-only" posture the code comment and runbook Β§1 both credited was never in effect: sessions have most likely been transmitting from every non-__DEV__ build, including the 2026-06-15 TestFlight build. TypeScript can't catch this β€” the RN options type carries an index signature.

2. Crash attribution was broken (independent defect)

applyAllowlist rebuilt exception.values as {type, value, stacktrace}, dropping mechanism; level was absent from ALLOWED_ERROR_FIELDS. The SDK's isHardCrash() requires mechanism.handled === false && mechanism.type === 'onerror', and beforeSend runs before envelope creation β€” so no JS fatal error ever marked its session crashed.

A crash-free-session-rate alert built on that would have counted native crashes only and read systematically optimistic, under-firing exactly when it mattered. Now preserved as explicitly type-guarded named scalars β€” never a spread, so the SDK's open mechanism.data bag can't ride along β€” plus an enum guard on level so it can't become a free-text channel.

3. Contract test

app/__tests__/privacy/releaseHealthSession.contract.test.ts (15 tests, runs in precommit via test:privacy). Red phase confirmed first: isHardCrash(scrubbed) returned false against the old code. The test imports the SDK's real isHardCrash, so an upstream change to the crash-attribution rule fails the build rather than silently zeroing the metric.

4. Docs reconciled to reality

  • privacy-policy.md Β§5.1 β€” previously promised Sentry receives "no device identifier", contradicted by the session did (a per-install UUID; no Sentry.setUser exists in app/src, so the iOS layer injects user.id = installationID). Replaced with an accurate disclosure.
  • dpia-sensitive-wellness-data.md v1.7 β€” retrospective control-status correction. Assessed not a breach: no wellness content exposed, and the value is a device-scoped install identifier, not PHR identifiable health information under 16 CFR Part 318. Not a Β§1 material-change trigger β€” Sentry was already an authorized sub-processor.
  • post-launch-monitoring-runbook.md Β§1 β€” its "no data source today" warning was wrong, and the interim Number-of-Errors alert it said would be superseded was never created (verified live: being-prod has one rule, the default onboarding issue alert, and zero metric alerts).

Session envelopes bypass beforeSend/normalizeDepth entirely (envelope session items, not events), so the controls the original AC named never applied to them; the review was done against the fixed session schema instead.

Compliance agent verdict: privacy-neutral β€” mechanism.type/handled/synthetic and level are all closed-vocabulary SDK-controlled values, and the app never constructs a mechanism anywhere.

Not claimed by this PR

Creating the crash-free-session-rate metric alert and verifying Releases β†’ Health require the Sentry dashboard and a new TestFlight build (the Sentry MCP is read-only for alert rules). Recorded as an operator checklist on the Notion item.

Verification

npm run precommit exit 0 β€” safety 108, clinical 106, unit 568, privacy 51 tests passed.

πŸ€– Generated with Claude Code

… attribution

Re-scoped after a planning panel falsified the story's premise. The item assumed
release-health sessions were deliberately disabled and needed enabling. They were
not disabled.

1. The option key was dead. `ExternalErrorReporter` passed `autoSessionTracking:
   false`, but @sentry/react-native reads `enableAutoSessionTracking`. The key we
   set does not exist in the SDK, is forwarded verbatim to the native layer, and
   is ignored there β€” and both native SDKs default session tracking ON. So the
   "privacy-first, error-only" posture the code comment claimed was never in
   effect: sessions have most likely been transmitting from every non-__DEV__
   build, including the 2026-06-15 TestFlight build. Corrected to the real option
   and kept ON intentionally. TypeScript cannot catch this class of bug β€” the RN
   options type carries an index signature.

2. Crash attribution was broken. `applyAllowlist` rebuilt exception.values as
   {type, value, stacktrace}, dropping `mechanism`; `level` was absent from
   ALLOWED_ERROR_FIELDS. The SDK's isHardCrash() requires
   mechanism.handled === false && mechanism.type === 'onerror', and beforeSend
   runs BEFORE envelope creation β€” so no JS fatal ever marked its session
   crashed. A crash-free-session-rate alert would have counted native crashes
   only and read systematically optimistic, under-firing exactly when it matters.
   Now preserved as explicitly type-guarded named scalars β€” never a spread, so
   the SDK's open `mechanism.data` bag cannot ride along β€” plus an enum guard on
   `level` so it can never become a free-text channel.

3. Contract test imports the SDK's REAL isHardCrash, so an upstream change to the
   crash-attribution rule fails the build rather than silently zeroing the metric.
   Runs in precommit via test:privacy.

4. Docs reconciled to reality: privacy-policy Β§5.1 disclosed session diagnostics
   and the per-install identifier (it previously promised "no device identifier",
   which the session `did` contradicts); DPIA v1.7 records the retrospective
   finding (not a breach β€” no wellness content, and Sentry was already an
   authorized sub-processor); runbook Β§1 corrected β€” its "no data source today"
   warning was wrong, and the interim Number-of-Errors alert it said would be
   superseded was never created (being-prod has zero metric alerts).

Session envelopes bypass beforeSend/normalizeDepth entirely (envelope session
items, not events), so the controls the original AC named never applied to them;
the review was done against the fixed session schema instead.

Compliance agent verdict: privacy-neutral β€” mechanism.type/handled/synthetic and
level are all closed-vocabulary SDK-controlled values, and the app never
constructs a mechanism anywhere.

NOT claimed by this change (require the Sentry dashboard + a new TestFlight
build; the Sentry MCP is read-only for alert rules): creating the crash-free-
session-rate metric alert, and verifying Releases -> Health. Recorded as an
operator checklist on the Notion item.

Verification: npm run precommit exit 0 β€” safety 108, clinical 106, unit 568,
privacy 51 tests passed.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MP2EZ
MP2EZ merged commit 86691af into development Jul 25, 2026
22 checks passed
@MP2EZ
MP2EZ deleted the chore/INFRA-295-sentry-release-health-sessions branch July 25, 2026 21:11
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