fix(orb): parse the AMS PR outcome from an anchored dedupKey, not a substring scan - #9754
Merged
JSONbored merged 1 commit intoJul 29, 2026
Conversation
…ubstring scan
ams_pr_outcome is the only notification kind whose rendered merged/closed copy
depends on a field that is not on the event: buildAmsPrOutcomeNotification recovered
the decision with event.dedupKey.includes(":merged:"). The dedupKey was the entire
carrier, yet nothing validated its shape -- normalizeAmsNotificationEventInput only
checked the key was a non-empty string -- so any ams_pr_outcome whose key lacked
":merged:" rendered as "closed without merge", including for a real merge. The
format also had two hand-duplicated producers with nothing pinning them together,
and the server-side builder has no non-test callers, so a reorder in the miner's
copy would silently flip every merge notification with nothing failing.
- ams-events.ts: add AMS_PR_OUTCOME_DEDUP_KEY_PATTERN (anchored, decision as a
capture group) and parseAmsPrOutcomeDecision, the single reader. The builder and
the pattern derive from one shared prefix constant -- the format is spelled once.
- normalizeAmsNotificationEventInput returns null for an ams_pr_outcome whose
dedupKey does not parse (the MCP/self-host path reaches the normalizer directly);
every other AMS kind's validation is unchanged.
- service.ts uses parseAmsPrOutcomeDecision; a null parse renders the CLOSED copy,
never an unproven merge (unreachable from ingest now the normalizer rejects it).
Adds a producer-parity test pinning the miner's buildAmsPrOutcomePayload output
against the server pattern for both decisions, mirroring the source-pinning drift
guard convention.
Closes JSONbored#9703
Contributor
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
Contributor
|
Important 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏳ LoopOver is waiting…LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting |
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9754 +/- ##
===========================================
- Coverage 90.27% 76.63% -13.65%
===========================================
Files 904 284 -620
Lines 113177 59529 -53648
Branches 26840 8505 -18335
===========================================
- Hits 102171 45618 -56553
- Misses 9676 13629 +3953
+ Partials 1330 282 -1048
Flags with carried forward coverage won't be shown. Click here to find out more.
|
JSONbored
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes #9703.
ams_pr_outcomeis the only notification kind whose rendered content depends on a field that is noton the event.
buildAmsPrOutcomeNotificationrecovered the merged/closed decision withevent.dedupKey.includes(":merged:")— the dedupKey was the entire carrier. Three things made thatunsafe:
normalizeAmsNotificationEventInputonly checked non-emptystring, so any
ams_pr_outcomewhose key lacked:merged:rendered as "closed without merge",including for a real merge.
of
decision/closedAtthere would silently flip every merge notification and nothing would fail.Change
ams-events.ts:AMS_PR_OUTCOME_DEDUP_KEY_PATTERN(anchored, decision as capture group) +parseAmsPrOutcomeDecision(the single reader). The builder and the pattern derive from one sharedprefix constant — the format is spelled once, not twice.
normalizeAmsNotificationEventInputreturnsnullfor anams_pr_outcomewhose dedupKey doesn'tparse (the MCP/self-host path reaches the normalizer directly, so validating only the HTTP zod schema
isn't enough); every other AMS kind's validation is unchanged.
service.tsusesparseAmsPrOutcomeDecision; anullparse renders the closed copy, never anunproven merge — unreachable from ingest now the normalizer rejects such a key upstream.
No
decisionfield is added to the event/schema (out of scope).Validation
no-decision, unknown-decision, and the loose-
:merged:-elsewhere trap).ams_pr_outcomekey while other AMS kinds keep their loose check.buildAmsPrOutcomeNotificationrenders the closed copy on a null parse.buildAmsPrOutcomePayloadoutput against the serverpattern for both decisions (the drift guard).