Architecture Decision Record · 0002

Canvas-Page-Region-Versions-Drift-Heilung

TL;DR — Drift in Canvas-Page-Region-Component-Hashes wird durch ein idempotentes Drush-PHP-Eval-Script geheilt, das aktive Versionen einspielt und anschließend drush cr auslöst.

Kontext

Drupal-Canvas (drupal/canvas) speichert pro Component-Instanz im component_tree-Feld einer Page (oder page_region-Config) den Hash der Component-Schema-Version zum Zeitpunkt des Speicherns. Beim Render-Versuch validiert Drupal\canvas\Entity\VersionedConfigEntityBase::assertVersionExists() diesen Hash gegen die heute im Component-Storage verfügbaren Versionen. Driftet der Hash (z. B. weil eine Drupal-Core-Aktualisierung den system_branding_block strukturell verändert und seine alte Version aus dem Storage entfernt), wirft Canvas eine OutOfRangeException und die gesamte Page rendert nicht mehr — auch nicht die unbeteiligten Header-/Footer-Bereiche, weil sie als page_region-Configs auf jeder Page eingebunden werden.

Im SOLCOM-Setup waren betroffen: 5 Hashes auf 2 page_region-Configs (byte_theme.header, byte_theme.footer) für die Components block.system_branding_block, block.system_menu_block.main, block.system_menu_block.footer und zwei SDC-Komponenten.

Entscheidung

Drift wird durch ein idempotentes Drush-PHP-Eval-Script geheilt, das alle page_region-Entities lädt, ihren component_tree durchläuft, jede component_version gegen die getActiveVersion() der zugehörigen Component-Entity vergleicht und bei Abweichung den aktiven Hash einsetzt. Die Page-Region wird neu gespeichert — Canvas validiert die neue Version und rendert wieder.

$cstor = \Drupal::entityTypeManager()->getStorage('component');
$active = [];
foreach ($cstor->loadMultiple() as $id => $ent) {
  $active[$id] = $ent->getActiveVersion();
}
$rstor = \Drupal::entityTypeManager()->getStorage('page_region');
foreach ($rstor->loadMultiple() as $rid => $region) {
  $tree = $region->get('component_tree');
  $dirty = FALSE;
  $newTree = [];
  foreach ($tree as $key => $item) {
    if (isset($active[$item['component_id']])
        && $active[$item['component_id']] !== $item['component_version']) {
      $item['component_version'] = $active[$item['component_id']];
      $dirty = TRUE;
    }
    $newTree[$key] = $item;
  }
  if ($dirty) {
    $region->set('component_tree', $newTree);
    $region->save();
  }
}
Anschließend drush cr ausführen. Das Script ist idempotent und kann bei erneutem Drift unverändert wieder laufen.

Rejected alternatives

Manuelles Editieren der Configs in config/sync + config:import rejected

Scheitert daran, dass die ungültigen Hashes auch im exportierten YAML stünden — der Reimport würde sie unverändert wieder einspielen.

Direkter UPDATE config SET data = REPLACE(...)-SQL rejected

Würde funktionieren, ist aber spröde gegen Sub-Component-Trees in Slots, mehrfach vorkommende Hashes, und hinkt nicht auf Folge-Drifts.

Page-Regions im Canvas-Editor neu speichern (UI-Pfad) rejected

Setzt voraus, dass die Site überhaupt rendert — was hier durch den Drift unmöglich war (Henne-Ei-Problem).

Consequences