Repository navigation
Conversation
…isplay name matches To best accomadate the current infrastructure, a new check was added to the publish workflow. This check will compare the display name of new extensions and new versions to existing data. For new extensions, exact matches will be blocked from upload. In the case of versions, extensions whose name changes to conflict will be blocked, but existing names will not be blocked. This carve out was added to address any existing conflict of display name without potentially blocking a legitimate version from being submitted. This grandfathered clause makes the assumption that the name squatting results will be monitored and addressed to get to a known good state. Once we get to a clean state, the grandfathered clause will effectively work as a standard exact match gate without any notable performance impact. Assisted-by: Claude Opus 5
Rebasing onto main revealed a second collision: main has since added V1_75, and the version used by "fix: Update migration file name to resolve runtime conflict" (V1_74) collided with V1_74__PersonalAccessToken_Column_Defaults.sql, also added by main since this branch diverged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PublishExtensionVersionHandler.java called token.getUser() and referenced a user variable that don't exist on main's AuthenticatedUser/UserData split; use userData / au.userData() like the rest of the class. Updated the test file's mocks to match (LoggedInAuthentication instead of a raw PersonalAccessToken). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
checkPublishPreconditions read ExtensionProcessor#getDisplayName(), which falls back to the extension name, while createExtensionVersion read the metadata's display name, which is null in that case. A package declaring no <DisplayName> was therefore held up against its extension name, and only on the paths that scan: every version bump of such an extension looked like a rename onto that name, locking the publisher out permanently if any other extension displayed it. Split the accessor into getDeclaredDisplayName(), authoritative and null when the package declares none, and getDisplayNameOrExtensionName() for the scan record that wants something readable. Fold the gate into one checkDisplayNameAdoption method that every call site goes through. Assisted-by: Claude Opus 5
|
@netomi the update I just pushed was the fix for #2081 (comment), which is the only code review item for this branch from the previous PR. |
|
Also, I can't add you to the PR as a reviewer, I'm assuming because you opened the PR. |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unicode whitespace bypasses normalization, and asynchronous activation leaves a concurrency window for duplicate names.
Review effort: Balanced
Findings: 3
Open (3)
What changed in this PR
Adds publish-time validation preventing cross-publisher display-name collisions while preserving mirrors and existing duplicates.
Changes:
- Adds normalized display-name lookup and database index.
- Enforces collision checks for new extensions and renames.
- Adds processor accessors and comprehensive tests.
| File | Description |
|---|---|
RepositoryServiceSmokeTest.java |
Covers the repository facade query. |
ExtensionJooqRepositoryTest.java |
Tests normalized collision lookup. |
RegistryAPITest.java |
Updates handler construction. |
PublishExtensionVersionHandlerTest.java |
Tests enforcement and exemptions. |
ExtensionProcessorTest.java |
Tests declared and fallback names. |
V1_76__ExtensionVersion_DisplayName_Index.sql |
Adds the normalized lookup index. |
ExtensionScanService.java |
Uses the display-label fallback. |
RepositoryService.java |
Exposes the collision lookup. |
ExtensionJooqRepository.java |
Implements active display-name lookup. |
PublishExtensionVersionHandler.java |
Enforces publish-time validation. |
ExtensionProcessor.java |
Separates declared and fallback names. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The display name conflict check trimmed only U+0020, so padding a name with any other space separator the validator admits (U+00A0, U+2000, U+3000, ...) bypassed it. Java, the query and the V1_76 index expression now strip the same set, defined once in DisplayNameUtil. The query also bound ev_match.active as a parameter, which a generic plan cannot match to the partial index's WHERE active. It is now rendered bare. A test prepares the rendered SQL under a forced generic plan and asserts the plan uses extension_version_display_name_idx. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
The change I pushed addresses the changes around whitespace and has an additional funky test that makes sure that the index with the whitespace calculations are used when that lookup is done, as it could become a seq scan if not handled properly. When discussing options w/ the AI, it raised that the Since these can be legitimate extensions that were squatted, we'll need to figure out some form of redress for it as well. |
Keep both the LONG_LIVED rename and the display name lookup in RepositoryServiceSmokeTest. Renumber the display name index migration to V1_81, since main now has its own V1_76 through V1_80. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
I think this check would be better as a
Things to handle:
The |
publish check
Per review, the check runs as a PublishCheck instead of inside
createExtensionVersion. Mirroring never goes through PublishCheckRunner,
so the mirrorEnabled flag and the duplicate call in
checkPublishPreconditions are gone, and results land in the scan audit
trail next to the other publish checks.
The scan now stores the package's declared display name
(extension_scan.declared_display_name, V1_82) so a package without one
is not compared under its extension name.
Decisions to note:
- ovsx.scanning.display-name-conflict.{enabled,enforced,required} all
default to true. This can be revisited before merge.
- Like every publish check, it only runs when ovsx.scanning.enabled is
true (default false), so instances without scanning no longer run it.
Also sets published_by on the ExtensionJooqRepositoryTest fixture, which
the published_by_id NOT NULL constraint from main requires.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Martin Lowe <martin.lowe@eclipse-foundation.org>
There was a problem hiding this comment.
🟡 Changes recommended
Duplicate Flyway versions block application startup, while an activation race and platform-insensitive lookups undermine display-name conflict enforcement.
5 open issues
Atomically reserve display names during activation · New Assign unique unused versions to new Flyway migrations · New Add display-name conflict options to deployment descriptors · New Scope latest display-name lookup to the target platform · New Scope grandfathering lookup to the scan target platform · New
1 resolved since last review
2 files not reviewed
server/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/ExtensionScan.java: Generated fileserver/src/main/jooq-gen/org/eclipse/openvsx/jooq/tables/records/ExtensionScanRecord.java: Generated file
Review effort: Balanced
| var conflict = repositories.findActiveExtensionByDisplayName(displayName, excludedNamespaces); | ||
| if (conflict == null) { | ||
| return Result.pass(); |
There was a problem hiding this comment.
Reactivation is out of scope. reactivateExtensions doesn't run the display name check. It only relies on scan status, so a version quarantined for a display name conflict stays inactive unless an admin allows it.
Versions that were active before are trusted when they come back. That leaves one known gap. If a publisher's extensions are inactive, for example after their agreement lapses, another publisher can take the display name in the meantime. When the first publisher reactivates, both extensions are active under the same name. We'll handle this in the upcoming rework for similar and duplicate names, which will need to deal with existing pairs anyway.
@netomi let me know if this aligns or if I should approach this another way.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Martin Lowe <mtlowe@live.ca>
Add condition to filter by target platform in latest query Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Martin Lowe <mtlowe@live.ca>
Two publishers could both pass the DISPLAY_NAME_CONFLICT publish check for the same name while neither version was active, then both activate after scanning. Activation now repeats the check under a transaction-scoped advisory lock on the normalized name, keeping the namespace exclusions and the existing-name exemption. A version that loses is quarantined rather than rejected, since the one that won may be the impersonator. The admin Quarantined tab now lists versions quarantined by a check (includeCheckQuarantines on /admin/scans and /counts), lets admins select them, and warns on allow which extension already shows the name. Reactivating previously active extensions is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Summary
Split out of #2081 (the publish-time enforcement half). Adds a new publish-time check that compares an extension's display name, case- and whitespace-insensitively, against every other publisher's active extensions, and rejects the publish on an exact match:
Backed by a new case/whitespace-normalized index on
extension_version.display_nameso the check stays cheap at scale.The other half of #2081 — the admin dashboard for moderating the existing (separate, unenforced)
NAME_SQUATTINGsimilarity check — is split out as #2267, since the two are independent (no shared code beyond two non-overlapping additions toRepositoryService).Test plan
./gradlew compileJava compileTestJava spotlessCheckpasses./gradlew test --tests PublishExtensionVersionHandlerTestpasses (the Postgres-backedExtensionJooqRepositoryTestcouldn't be run here — no Docker daemon in this environment)🤖 Generated with Claude Code