Draft: Keep three cross-lane singletons out of the infection system

What does this MR do and why?

This MR adds three modules to INFECTION_BLOCKLIST in config/helpers/context_aliases_shared.js:

  • ee/app/assets/javascripts/ai/events/panel.js, the Duo panel event hub
  • app/assets/javascripts/helpers/event_hub_factory.js, which builds that hub and 35 others: createEventHub() has 36 call sites across app/ and ee/app/
  • ee/app/assets/javascripts/ai/duo_agentic_chat/context/external_context_store.js, which holds a Set of context providers

EE page code under work_items, boards, vue_merge_request_widget and ci/pipeline_details emits on the Duo panel hub. Several of those pages run Vue 3 behind rollout flags. The hub therefore crosses the lane boundary today.

It works because none of the three modules reaches Vue, so both lanes share one copy. Nothing declares that requirement. One Vue import added below any of them gives each lane its own copy.

The failure is silent. A duplicated hub still builds and still accepts emits. The emits reach no listener on the other lane. panel.js also holds a module-level pendingScrollToSessions flag, which splits with it.

INFECTION_BLOCKLIST means "never duplicate this module", so it states the requirement and enforces it.

Why the blocklist is safe here

All three modules are free of Vue. Forcing one shared copy cannot give a Vue 3 page an object built for Vue 2. That is the condition the list needs. A Pinia instance or a VueApollo provider is bound to its Vue version, and neither can go on the list.

The list is silent by design. A developer who adds a Vue import to one of these modules gets a shared copy, and no error. An event hub and a Set registry have no reason to need Vue, so a shared copy is the correct result.

What this does not cover

The hubs that the factory builds are not covered. Each event_hub.js calls createEventHub() itself, so each one reaches Vue on its own and gets a copy per lane.

Those hubs hold a detected singleton, so the duplicated modules check in !252666 (merged) reports them. That check reports a module only when one page loads it in both lanes. It does not report a module that could split later.

That check does not detect external_context_store.js. A module-scope new Set() is shared state, and the detector does not recognise that shape.

How to verify

Run the infection scanner to confirm the entries change nothing today, because all three modules are already clean and the scanner graph is byte-identical with and without them.

node scripts/frontend/infection_scanner/infection_scanner.mjs
FOSS_ONLY=true node scripts/frontend/infection_scanner/infection_scanner.mjs

Both exit 0.

To show the entries hold, add a Vue import below one of the modules and run the scanner again.

printf "import Vue from 'vue';\n\n%s" "$(cat app/assets/javascripts/helpers/event_hub_factory.js)" \
  > /tmp/f && cp /tmp/f app/assets/javascripts/helpers/event_hub_factory.js
node scripts/frontend/infection_scanner/infection_scanner.mjs

This table shows the resolution predicate for the three modules:

module without these entries with them
helpers/event_hub_factory.js duplicated shared
ee/…/ai/events/panel.js duplicated shared
ee/…/context/external_context_store.js shared shared

The third row is unchanged because the Vue import is not in its subtree. Revert the import afterwards.

No changelog entry, because this is build tooling with no user-facing change.

Merge order

This MR targets 625296-fix-cross-lane-duplication, the branch of !253161 (merged), because that MR also edits INFECTION_BLOCKLIST.

GitLab retargets this MR to master when the parent merges.

It is a sibling of !252666 (merged) and not a dependency of it. The two touch different files.

References

Edited by Miguel Rincon

Merge request reports

Loading
Loading