Repository navigation
feat(events)!: Convert event automation identifiers to filters - #2442
sentry-junior[bot] wants to merge 8 commits into
Conversation
Each plugin can now turn its saved identifiers into the filters that select the same events (`identifierMatch` in plugin-api). Core uses it to save new and edited event automation triggers with no identifier, and the heartbeat converts saved rows one at a time through the locked, versioned save. A trigger converts only when every selected event gets the same filters, the filters agree with saved filters, and the result passes the catalog check. Otherwise the row keeps its identifier and keeps running, so no trigger gets broader. Slack channel automations now save a `channelId` filter for their destination channel. Sentry project identifiers do not convert for metric alert events, because those events can leave out `projects`. An omitted `match` on update now keeps the saved filters. Watches keep exact identifiers. The identifier column stays for slice D. Refs #2362 Co-Authored-By: David Cramer <david@sentry.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Sentry Evalsjunior-behavioral
Base commit junior-guardian
Base commit junior-integration
Base commit junior-router
Base commit |
A trigger that cannot convert to filters now saves the reason in
`identifierReason` through the same locked, versioned save. The heartbeat
checks at most 50 rows per tick that have an identifier and no saved
reason, so it checks each row once. It skips rows whose identifier column
and saved trigger disagree, because a save would make them broad. Edits
that change the trigger set the reason again.
The trigger description shows the reason as a note, not a problem, in the
dashboard details, Slack details, and listEventAutomations. Rows that keep
their identifier stay active.
The update tool description now says plainly that leaving out match keeps
the saved filters, and only `{}` removes them.
Refs #2362
Co-Authored-By: David Cramer <david@sentry.io>
This comment has been minimized.
This comment has been minimized.
Updates now change match filters key by key. A left-out filter keeps its saved value and null removes it, so an update cannot drop the scope that a conversion moved into match. A write whose saved filter never matches its identifier is rejected instead of saved as a trigger that never runs. Move the heartbeat backfill of saved rows to a follow-up, because no release can read rows with no identifier yet. Drop the Sentry converter, because the plugin has no saved install org to check identifiers against. Pass the one pull request to the lazy GitHub gate, mark identifierMatch as temporary, and test identifiers that do not rebuild and events that need different filters. Refs #2362 Co-Authored-By: David Cramer <david@sentry.io>
The version schema accepts editedBy "junior" and the dashboard shows Junior as the editor. Nothing writes it yet. The reader ships first, so the follow-up that converts saved triggers can roll back to this release and keep version history readable. Refs #2362 Co-Authored-By: David Cramer <david@sentry.io>
|
4 screenshot changes — 4 changed · 0 added · 0 removed Review screenshots in Frameshift
|
A repository identifier and a repo filter now save the same trigger with no identifier, so the eval checks the saved trigger. Refs #2362
|
the pr description is far too technical and it doesnt explain to me what we're actually doing, what the actual final interface/configurations look like, and what might break |
|
is there value in supporting the identifier the way we are? like since its just duplicating matches and it'd be legacy? rying to understand the API interface. maybe we tihnk the simplicity is required to prevent agent errors or something? what about idempotency keys and deduplication how will that function? |
Since #2440 (`15ee5c130`), an automation run no longer writes its saved visibility to the Destination row. Only a live Slack signal does. A run into a channel with no row stores `unknown`, and readers treat it as private. In production, the Slack thread where a person created the Automation records the channel. The eval fixture inserted Automations without that thread, so their channels stayed `unknown`. The fixture reads results as `WEB_VIEWER_EMAIL`, who is not a participant. So `readConversationAccessFromSql` returned `canViewPrivateContent: false`, and the reporting API hid tool payloads for every scheduled and event automation run. Tool calls came back with no input and no output (`result null [4B]` on every tool). `finishAutomationRun` results and tool arguments were empty. This broke these cases on unrelated branches (#2435, #2442, fix/flaky-behavioral-evals, chore/remove-slash-command): - Integration: `Event automation delivery` (no-match silence ×2, blocked automation) and `Scheduled Delivery` (no message outcome) - Behavioral: `Scheduled Delivery` (post in this channel, provider that Junior does not have) and `Sentry Skill Workflows` (creator-bound scheduled Sentry work) There is no product change. Fail-closed `unknown` is intended (#2440). Automations are created from a live Slack turn in their channel, so production channels have a known visibility. ## Fix `packages/junior-evals/src/fixture/insert.ts`: `insertScheduledAutomation` and `insertEventAutomation` now store the creation thread through the Conversation store with `visibility: "public"`, as the live Slack event would. No assertions, cases, or timeouts change. ## Checks - `pnpm lint`, `pnpm typecheck`, `pnpm --filter @sentry/junior-evals test` pass. - A throwaway harness check (not committed) used the real insert functions. A dispatch-shaped write into a bare `slackChannel()` was redacted (`unknown`). Channels from either insert function were readable (`public`). - The 7 affected eval cases did not run locally: the sandbox has no `cloudflared` for eval egress. CI must confirm them. <!-- junior-request-attribution:start --> via **David Cramer**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A5d3914babce549420a1ad7b950c41799 --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A5d3914babce549420a1ad7b950c41799) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A5d3914babce549420a1ad7b950c41799/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
…-filter-conversion
…-filter-conversion # Conflicts: # packages/junior-dashboard/e2e/automation-editor.spec.ts # packages/junior-dashboard/src/client/pages/automations/AutomationDetails.tsx # packages/junior-dashboard/src/client/pages/automations/AutomationVersionsPage.tsx
|
the pr description here needs to be simplified. its somewhat easy to understand but its an essay |
|
gonna need a rebase |
There was a problem hiding this comment.
The end state in #2362 is right: an event automation trigger is namespace + events + match, and only Watches keep exact identifiers. The middle step in this PR is the weak part. It keeps identifier as agent input, saves something else, and adds rules to make the two forms agree.
- Two inputs for one scope. The agent sends
identifier: "getsentry/junior#7"and gets backidentifier: nullplusmatch: { repo, pullRequest: 7 }. Then the two forms conflict. Example: the saved trigger is converted, and the agent retargets withidentifier: "getsentry/junior#8".changedMatchkeeps the savedpullRequest: 7,triggerWithoutIdentifierfinds a conflict, and the update fails with "the pullRequest filter never matches". This happens even whenmatchis left out. Simpler: cut over now for automation input. Removeidentifier/resourceTypefrom the event trigger increateAutomation/updateAutomation, and addmatchnext toidentifierinsubscribableResourceSchema. Plugins already have the structured parts when they build the identifier (gitHubPullRequestResource({ repo, number })), so no regex parse is needed. ThenidentifierMatchand its parse code are needed only for the #2447 backfill, not for every create and update. Agents keep the "copy it from the tool result" path, so the cutover should not add agent errors. - Out of date with main. #2496 replaced
update-event-automation.tswithautomations/tools/update.ts. That tool's schema says "Complete replacement trigger". The key-by-keymatchmerge must move there, and that description must change, or the two contradict. The PR also conflicts with main, andintegration / shard 1/2andevalsfail. - Sentry keeps the identifier path alive. If Sentry triggers never convert, slice D cannot drop the column, and "temporary"
identifierMatchstays. Decide now. One option: new Sentry triggers useorg/projectsfilters taken from tool results, and only old Sentry rows keep identifiers until they are checked.
On dedupe: it still works. The dispatch key and the run-limit key are both hash(automationId, namespace, eventKey), and every fanned-out copy shares one eventKey. So a filter-only row that matches the PR copy and the repo copy runs once. One cost changes: converted rows stop using the indexed identifier IN (...) lookup. Every copy of every event now reads and checks in JS each active row with no identifier in that team, namespace, and event type. At today's row counts that is fine.
|
Closing in favor of #2572, which does a hard cutover on agent input: event triggers are |
…rs (#2572) Hard cutover on agent input for event automation triggers, as proposed in the [#2442 review](#2442 (review)). An event trigger from `createAutomation` / `updateAutomation` is now `namespace + events + match`. Watches keep exact identifiers. Refs #2362. **Tool input** - `identifier` and `resourceType` are gone from the event trigger schema of both tools. - `updateAutomation` changes `match` key by key: a value sets a filter, `null` removes it, and a filter left out keeps its saved value. So an update can't broaden a trigger by leaving a filter out. A namespace change starts with no filters. - `searchEventTypes` now describes reach in terms of `match`. `reach: "resource"` events (timers, workspace snapshots) are only usable by Watches. **Subscribable results** - `SubscribableResource` gets an optional `match`. It is missing for Watch-only resources. - GitHub fills it from the parts it already has: PR `{repo, pullRequest}`, issue `{repo, issue}`, repo `{repo}`, deployment `{repo, commitSha, environment?}`, release `{repo, tag?}`. Vercel deployment fills `{projectId, target?, commitSha?}`. No identifier parsing. - Linear and Sentry have no subscribable builders. Their match fields (`teamKey`/`issueIdentifier`, `projects`/`issueId`/`alertRuleId`) already cover single-resource scoping, so Sentry does not block dropping the identifier. **Slack channel messages** - `message.posted` moves from `reach: "resource"` to `{ anyOf: ["channelId"] }`. The tools set `channelId` from the destination channel, and `requireSlackChannelMessageScope` still rejects other channels. **Saved triggers with an identifier** - They keep running, and dispatch is unchanged. Tool edits in the same namespace keep the saved identifier. - Known gap until #2447 converts saved rows: if an agent adds a filter that conflicts with a saved identifier, such as `pullRequest: 8` on a row saved as `#7`, the trigger stops matching. Core can't detect that without the plugin conversion that #2447 adds. - The dashboard editor still edits identifiers on saved rows. It should switch over with #2447. **Dedupe:** no change. Runs are keyed on `(automationId, namespace, eventKey)`, so a filter-only automation that matches the PR copy and the repo copy of one webhook still runs once. **Verification** - `pnpm typecheck` and `pnpm lint` pass. - Automation integration suites pass (66 tests), as do the GitHub plugin suite (266) and the Vercel plugin suite (33). - In the full `@sentry/junior` run, these failed in this sandbox: `agent-dispatch-work`, `annotation-cards`, `durable-queue`, `event-wake-delay`, `bot-image-hydration`, and the generated-title assertion in `event-automations`. They also fail on unmodified `main` in the same sandbox (mostly 5s timeouts), so I'm treating them as environment failures. CI is the real check. - I didn't run the evals locally. Updated assertions are in `event-automations/management`, `credentials`, and `guardian/scheduled-work`. <!-- junior-request-attribution:start --> via @dcramer. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC0B595QDZLL%3A1793654465.198809 --> -- [View Session](https://junior-prod.sentry.dev/conversations/slack%3AC0B595QDZLL%3A1793654465.198809) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC0B595QDZLL%3A1793654465.198809/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: David Cramer <23610+dcramer@users.noreply.github.com>
What this does
Today an event automation can point at one thing in two ways:
getsentry/junior#7, orrepo: getsentry/junior, pullRequest: 7.#2407 added filters. This PR starts to move everyone to filters, so later we can delete identifiers (#2362).
When someone creates or edits an event automation and gives an identifier, Junior now saves filters that match exactly the same events. The automation runs exactly as before. It does not start to run for more things.
This PR does not change automations that are already saved. They keep running as they are. A separate draft PR, #2447, converts them later.
Refs #2362
What it looks like
Someone asks: "tell me when a review is posted on #7".
The agent still sends this to
createEventAutomation:{ "namespace": "github", "resourceType": "pull_request", "identifier": "getsentry/junior#7", "events": ["pull_request.review.commented"] }Junior now saves this:
{ "namespace": "github", "events": ["pull_request.review.commented"], "match": { "repo": "getsentry/junior", "pullRequest": 7 } }The dashboard and the automation details show the filters.
What each plugin converts:
ENG-123)When Junior keeps an identifier, the automation still works. Its details show a short note that says why.
Sentry keeps identifiers because each Sentry identifier contains an org name, and Junior has no way to check that org. If Junior converted an identifier with an old or wrong org, an automation that never ran could start to run for the wrong things.
Changes to updating an automation
This is the part that changes behavior. It only affects
updateEventAutomation, the tool that the agent uses.Before this PR, a new
matchreplaced all saved filters. After conversion, the filters are the thing that keeps an automation pointed at one PR. So "also skip drafts" could quietly turn "PR #7" into "every PR in the repo".Now
matchchanges only the filters that it names:match: { isDraft: false }repo,pullRequest: 7repo,pullRequest: 7,isDraft: falsematch: { pullRequest: null }repo,pullRequest: 7repo(every PR in the repo)match: { pullRequest: [7, 8] }repo,pullRequest: 7repo,pullRequest: [7, 8]match: {}match: { repo: null }repo,pullRequest: 7repoThe identifier rules from #2407 stay the same: leave out
identifierto keep it, and set it tonullto drop it.The dashboard editor shows every filter and saves the whole trigger, so it works the same as before.
Junior now also rejects a trigger that can never run. An example is identifier
getsentry/junior#7with filterpullRequest: 5. The error names the filter. Before, Junior saved the trigger, and it never ran.What might break
match: {}used to remove all filters. Now it does nothing. To remove a filter, set it tonull.matchused to drop the filters that it left out. Now they stay.identifierMatch, that turns an identifier into filters. Plugins that do not add it keep identifiers. It is temporary, and a later change removes it with identifiers.Smaller changes
Checks (head
e9711e4c3)main. Some event automation delivery evals and one scheduler eval also fail on other open PRs. These failures are not caused by this PR.--
View Session [Sentry]