Manifest : dédup par version, l'entry channel-spécifique gagne sur default
Cas reporté : un client firefighter recevait DEUX entries v1.5.3 (default + firefighter) parce que la sémantique additive est correcte pour la visibilité, mais ne dédupe pas. Le launcher faisait alors un ToDictionary(v => v.Version) qui collisionne sur "1.5.3" et garde silencieusement le premier (la default), donc le mauvais ZIP s'affichait dans la UI. Fix : Manifest.php groupe par version après le filtre, et pour chaque groupe à plusieurs entries pick l'entry la plus spécifique au client : 1. priorité à celle taggée avec le channel propre du client (firefighter) 2. sinon l'entry default sert de fallback Sémantique pour un user firefighter : - v1.5.3 (default) + v1.5.3 (firefighter) → renvoie uniquement firefighter - v1.4.0 (default uniquement) → renvoie default (visible) - v1.5.3 (firefighter uniquement) → renvoie firefighter Fix purement server-side, pas de modif client requise. Le launcher recevra naturellement un seul ZIP par version, le bon. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,14 +68,28 @@ final class Manifest
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Filtre + dédupe par numéro de version.
|
||||||
|
*
|
||||||
|
* Étape 1 — filter : on garde les versions visibles par ce client. Un
|
||||||
|
* client sur channel X voit les entries taggées 'default' OU 'X'.
|
||||||
|
*
|
||||||
|
* Étape 2 — dedupe : pour chaque numéro de version qui apparaît plusieurs
|
||||||
|
* fois (cas v1.5.3 default + v1.5.3 firefighter), on ne renvoie qu'UNE
|
||||||
|
* seule entry — la plus spécifique au client. Priorité : l'entry taggée
|
||||||
|
* avec le channel propre du client gagne ; à défaut, l'entry default
|
||||||
|
* sert de fallback. Sans cette dédup côté serveur, le launcher recevait
|
||||||
|
* deux entries v1.5.3 et son `ToDictionary(v => v.Version)` collisionnait
|
||||||
|
* silencieusement, ce qui faisait apparaître dans l'UI le mauvais ZIP
|
||||||
|
* pour un user firefighter.
|
||||||
|
*
|
||||||
* @param array<int, array<string,mixed>> $versions
|
* @param array<int, array<string,mixed>> $versions
|
||||||
* @return list<array<string,mixed>>
|
* @return list<array<string,mixed>>
|
||||||
*/
|
*/
|
||||||
private static function filterVersions(array $versions, ?string $clientChannel): array
|
private static function filterVersions(array $versions, ?string $clientChannel): array
|
||||||
{
|
{
|
||||||
$filtered = [];
|
// Étape 1 : filter visible par ce client
|
||||||
|
$visible = [];
|
||||||
foreach ($versions as $v) {
|
foreach ($versions as $v) {
|
||||||
// Si pas de channels sur l'entry → implicitement "default"
|
|
||||||
$channels = (isset($v['channels']) && is_array($v['channels']) && !empty($v['channels']))
|
$channels = (isset($v['channels']) && is_array($v['channels']) && !empty($v['channels']))
|
||||||
? $v['channels']
|
? $v['channels']
|
||||||
: ['default'];
|
: ['default'];
|
||||||
@@ -84,9 +98,42 @@ final class Manifest
|
|||||||
$matchesUser = $clientChannel !== null && in_array($clientChannel, $channels, true);
|
$matchesUser = $clientChannel !== null && in_array($clientChannel, $channels, true);
|
||||||
|
|
||||||
if ($isPublic || $matchesUser) {
|
if ($isPublic || $matchesUser) {
|
||||||
$filtered[] = $v;
|
$visible[] = ['entry' => $v, 'channels' => $channels];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return array_values($filtered);
|
|
||||||
|
// Étape 2 : group by version, pick most specific per group
|
||||||
|
$byVersion = [];
|
||||||
|
foreach ($visible as $item) {
|
||||||
|
$key = (string)($item['entry']['version'] ?? '?');
|
||||||
|
$byVersion[$key][] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($byVersion as $items) {
|
||||||
|
if (count($items) === 1) {
|
||||||
|
$result[] = $items[0]['entry'];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Plusieurs entries pour ce numéro : on cherche d'abord une qui
|
||||||
|
// matche le channel spécifique du client (firefighter, police…),
|
||||||
|
// sinon on retombe sur l'entry default.
|
||||||
|
$specific = null;
|
||||||
|
$defaultEntry = null;
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($clientChannel !== null
|
||||||
|
&& $specific === null
|
||||||
|
&& in_array($clientChannel, $item['channels'], true)) {
|
||||||
|
$specific = $item;
|
||||||
|
}
|
||||||
|
if ($defaultEntry === null && in_array('default', $item['channels'], true)) {
|
||||||
|
$defaultEntry = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$picked = $specific ?? $defaultEntry ?? $items[0];
|
||||||
|
$result[] = $picked['entry'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user