Skip to content

fix(runner): gate the Outlook-scanner and edgeless-504 populations at beforeSend (DEV-2858) - #325

Merged
demtario merged 1 commit into
masterfrom
fix/DEV-2858-beforesend-suppression-gates
Sep 9, 2026
Merged

fix(runner): gate the Outlook-scanner and edgeless-504 populations at beforeSend (DEV-2858)#325
demtario merged 1 commit into
masterfrom
fix/DEV-2858-beforesend-suppression-gates

Conversation

@demtario

@demtario demtario commented Sep 9, 2026

Copy link
Copy Markdown
Member

Two beforeSend suppression gates for populations the DEV-2852 triage classified NOT-OURS: Sentry DEMOS-5F (24 events) and DEMOS-9 (786 events — the largest issue in the project).

Why this is code and not a dashboard filter

Sentry custom message filters are plan-locked on this project (one DSN across all three environments, so rate limits can't be per-environment either). Message-shaped filtering has to be a code gate. There is no dashboard-only option.

Where the predicates live

A new zero-import module apps/authoring/src/eventGate.ts, not inline in sentry.ts. That file reads import.meta.env and imports @sentry/react, so nothing in it can be unit-tested — the same reasoning that produced the existing reportingGate.ts. Parameters are structurally typed with no imports at all (node --experimental-strip-types cannot resolve @sentry/react types or ./x.js specifiers).

Both gates are inserted immediately after isUnhandledNoise and before the surface === DEMO_SURFACE re-homing branch. isForeignUnhandled sits after that branch because relays carry preview-origin frames by definition; neither new gate needs that protection, and the insertion comment records the discriminator rather than just the issue id — 5F requires mechanism.handled === false while every relay arrives via captureException (handled: true), and DEMOS-9 requires tags that reportDemoEvent never sets.

Gate 1 — isOfficeScannerRejection (DEMOS-5F)

The Microsoft Outlook / Office safelink scanner injecting script into the page. Unhandled rejection, no first-party stack frame, nothing fixable on our side.

Conjuncts: some exception.values[] has mechanism.handled === false and its value or type matches /Object Not Found Matching Id/i.

The integers after Id: and ParamCount: are deliberately left out of the pattern rather than \d+-ed, and MethodName: is excluded entirely — it varies in the wild (update, getInstance, …) and the prose is already discriminating. Degrade direction, in the house style: the string comes from third-party injected script, so if Microsoft rewords it the predicate stops matching and the event reports again — noisy, never silent.

Gate 2 — isEdgelessForeignSessionStart (DEMOS-9)

Confirmed NOT-OURS by its own DEV-2559 instrumentation: session_response_origin=foreign, session_response_type=basic, session_elapsed_bucket=<1s clustered (a fixed ceiling above our handler, not slow container boots), and cf_ray empty on all 25 sampled foreign-origin events — the request never reached our Cloudflare edge, so something upstream answered.

Verified this is reported client-side: ContainerRuntime.mount ships in the browser bundle and the report site is in App.tsx. It does not belong worker-side — workers/api/src/sentry-gate.ts only resolves DSN/environment and re-homes budget alerts, and more decisively, a 504 that never reached our edge produced no Worker invocation at all.

context === "tier2-session-start" is a scoping conjunct so a future reuse of session_response_origin elsewhere isn't silently silenced. session_status is deliberately not a conjunct, matching the existing precedent that "the tier turns on where the response came from, not on the status."

The !cf_ray conjunct is unreachable today. Please don't delete it.

sessionDiagnostics.ts returns "cloudflare" whenever a ray is present, so "foreign" already implies no ray, and foreign + cf_ray cannot occur right now. It looks like dead code, and there is a house rule that would seem to license removing it — the one saying a predicate must not recognise a wording no event has produced on its path, because "widening this predicate for it would be a guess dressed as a fact."

That rule forbids widening to unobserved inputs, because widening is what silences unverified things. !cf_ray is a narrowing conjunct: it can only ever suppress less, never more. Direction is the discriminator. It is what keeps the gate correct if the responseOrigin taxonomy is ever changed to emit "foreign" for a ray-bearing response — and a ray-bearing 504 is our side of the edge, i.e. the real capacity signal we must not silence. The code carries this argument as a comment, and test N1 pins it with an input that cannot occur yet.

This does not zero the 786

The ~88 events predating the DEV-2559 instrumentation carry no diagnostics tags at all and keep reporting. That is correct: an untagged event carries no evidence of being not-ours.

Verification

  • pnpm typecheck — run first, deliberately, since the real risk was assignability between Sentry.ErrorEvent's mechanism.handled optionality / tags index signature and the structural shapes. All 4 packages green.
  • pipeline/sentry-gating.test.mjs — 28/28
  • pnpm test1057 tests / 1055 pass / 0 fail / 2 todo (todos pre-existing)

Revert-check, with a correction worth recording. The instructed check — comment out the two sentry.ts wiring lines — turned out to be a no-op: the test file imports eventGate.ts directly and never loads sentry.ts. Verified empirically (28/28 still passed). The meaningful check was hollowing both predicates to return false: 24 pass / 4 fail, and the 4 failures were exactly the 4 positive cases (5F-P1, 5F-P2, 9-P1, 9-P2), with every negative staying green.

Negative coverage is the point of this change, so it's spelled out: a DEMOS-9-shaped event with a cf_ray still reports; origin: "cloudflare" + ray still reports; "unreadable" (every cross-origin dev machine) and "headerless" still report, so one taxonomy value is suppressed rather than three; an event with no diagnostics tags still reports; a different context still reports; and for 5F, the same message with handled: true still reports.

Known gap, flagged rather than hidden

The two beforeSend call sites themselves have no test coverage. A typo, an inverted condition, or an accidental deletion of either line would go undetected, because sentry.ts cannot be loaded under plain Node. This is not new — it is the same structural limitation the file already has for isUnhandledNoise and the DEMO_SURFACE re-homing branch, and sentry-gating.test.mjs's own header comment acknowledges it. Non-blocking, but a future reviewer should not assume the wiring is pinned just because the predicates are.

DEMOS-4P deferred, on structural grounds

The task originally scoped a third item: fix the preview harness's componentDidCatch. There is no componentDidCatch or getDerivedStateFromError anywhere in the repo — the generated React harness is a bare createRoot(...).render(...). So the boundary that produced The above error occurred in the <ExampleComponent> component: is not one we own; it's the framework's own boundary for the Tier-2 SSR frameworks, or one inside the previewed project. Both viable fixes land in packages/runtime/src/monitor.ts, which DEV-2853 owned. Now that DEV-2853 has merged, this is unblocked as a follow-up — recommended scope is teaching the console interceptor to recognise React's two-part output, which currently falls through to a stackless console-error.

Also withdrawn from this task: the original claim that DEMOS-18 / DEMOS-2N bypass reportDemoEvent. They don't. But a bypassing population does exist — DEMOS-1H and DEMOS-1E, which carry no DemoError name at all — and those are left unresolved in Sentry with a note pointing here.

🤖 Generated with Claude Code


Note

Medium Risk
Changes Sentry ingest filtering for real production errors; mistakes could drop actionable session-start signals, though narrow conjuncts and extensive negative tests aim to prevent that.

Overview
Adds code-side Sentry suppression for two triaged NOT-OURS populations (DEMOS-5F, DEMOS-9) because dashboard message filters are plan-locked on the shared DSN.

A new import-free module eventGate.ts holds isOfficeScannerRejection (unhandled mechanism.handled === false plus Microsoft safelink scanner wording) and isEdgelessForeignSessionStart (tier2-session-start + session_response_origin: foreign + no non-empty cf_ray). sentry.ts wires both into beforeSend right after isUnhandledNoise and before demo-runtime re-homing, with comments explaining why relays are unaffected.

pipeline/sentry-gating.test.mjs pins positive/negative cases for both predicates. run-and-deploy.md notes these gates stay when demo-runtime monitoring is removed.

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

…ulations (DEV-2858)

Adds two beforeSend gates, in a new import-free apps/authoring/src/eventGate.ts,
for populations the Sentry triage classified NOT-OURS: the Microsoft
Outlook/Office safelink scanner's injected unhandled rejection (DEMOS-5F, 24
events) and the edgeless-foreign Tier-2 session-start facet (DEMOS-9, 786
events). Pinned by new cases in pipeline/sentry-gating.test.mjs.

This is code, not a Sentry dashboard filter, because custom message filters are
plan-locked on this project (one DSN shared across all three environments) —
there is no per-environment filter to add instead.

isEdgelessForeignSessionStart keeps a `!cf_ray` conjunct that is unreachable
today: sessionDiagnostics.ts's responseOrigin() only ever returns "foreign" when
there is no ray, so every event this gate can currently see already lacks one.
It stays anyway. The session-lifecycle.ts precedent that forbids guessing at
unobserved inputs applies to WIDENING a predicate to a new case with no
evidence; `!cf_ray` is the opposite, a NARROWING conjunct that can only ever
suppress fewer events than the gate would without it, never more. It is what
keeps the gate correct if the origin taxonomy ever starts emitting "foreign" for
a ray-bearing response — which would be our side of the edge, and the real
capacity signal DEMOS-9 exists to protect.

This does not zero DEMOS-9's 786 events: ~88 of them predate the DEV-2559
diagnostics-tag instrumentation and carry no tags at all, so they keep
reporting — correctly, since an untagged event carries no evidence of being
not-ours.

DEMOS-4P (the preview harness's apparent componentDidCatch) is deferred, not
implemented here: grepping all source for componentDidCatch and
getDerivedStateFromError returns nothing in this repo. The boundary that
produced that Sentry title belongs to the framework or the previewed project,
not to code this repo owns, so no gate here can reach it.

Verification: pnpm typecheck (run first, before writing tests, to catch any
assignability mismatch between Sentry.ErrorEvent's optional
exception.values[].mechanism.handled / tags index signature and eventGate.ts's
structural types — none found), the 28 new/existing sentry-gating cases via
node --experimental-strip-types --test, and the full pnpm test suite (1057
tests, 1055 pass, 2 pre-existing todo, 0 fail). Revert-check: hollowing both
predicates to `return false` flips exactly the 4 positive cases red (24 pass /
4 fail) while all negative cases stay green, confirming the tests exercise the
real predicates rather than tautologies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@demtario
demtario merged commit 3ac55d8 into master Sep 9, 2026
6 checks passed
@demtario
demtario deleted the fix/DEV-2858-beforesend-suppression-gates branch September 9, 2026 09:29
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.

2 participants