— Artikel — № 122

122 —AI

Magento 1 AI-productteksten: staging die standhoudt

Een read-only staging-kopie is de goedkoopste verzekering tegen AI-teksten die de verkeerde SKU overschrijven in een Magento 1-shop uit 2014.

Linnen bureau van bovenaf met papieren Magento-plan, manilamap, SKU-vel, koperen plaatje, lakzegel, vulpen.
Hero · gestileerd stilleven№ 122

De Loom van 04:12

De Loom kwam binnen om 04:12 op een dinsdag. Een lead van een Rotterdams bureau, midden in een zin, deelde zijn scherm met een Google Sheet vol 8.400 productbeschrijvingen die een klant met "AI herschreven" wilde hebben en vrijdag live op een Magento 1.9-shop gezet. De shop was voor het laatst gepatcht in 2021. De SKU's hingen vast aan een Snelstart-boekhoudkoppeling die bij elke sync short_description opnieuw inlas. Twee developers. Een lang weekend. Een spreadsheet die in zijn huidige vorm één foute UPDATE verwijderd was van 8.400 kapotte facturen en een vastgelopen indexer.

Dit is op dit moment het meest voorkomende soort AI-klus op een verouderde site dat we zien, en het deel dat de klant bijna nooit ziet is precies het deel dat bepaalt of zijn shop maandag draait: niet de prompt, niet het model, ook niet de kwaliteit van de tekst. De vorm van de staging-omgeving.

Als je op het punt staat AI-gegenereerde productteksten in een Magento 1-catalogus te gieten, is er maar één mening die ertoe doet: het raakt de live database niet voordat een read-only kopie dezelfde writes heeft opgeslokt.

De fragiele naden van de EAV-catalogus

De EAV-catalogus van Magento 1 is fragiel op manieren die AI-bulkwrites juist verergeren. Eén productbeschrijving woont verspreid over minstens vier tabellen: catalog_product_entity, catalog_product_entity_text voor description, catalog_product_entity_varchar voor short_description, en catalog_product_flat_1 als flat catalog aanstaat. Elke store view heeft zijn eigen rij. Elke wijziging van een attribuut vraagt om een reindex van catalog_product_flat, catalog_product_attribute en catalogsearch_fulltext, anders verschijnt de nieuwe tekst niet in de storefront en is hij niet doorzoekbaar.

Er is geen versiegeschiedenis in de Magento 1-admin. Er is geen soft delete op attribuutwaarden. Als je UPDATE met het verkeerde store_id-filter liep, is de originele tekst weg. Magento 1 ging in juni 2020 officieel end-of-life volgens de productpagina van Adobe, wat neerkomt op: geen security-backports en, relevanter hier, geen vriendelijk migratiepad weg van slechte data. Alles op PHP 8.x draait al zonder officiële support, en de deprecation-notes in de PHP 8.2-migratiegids bijten de EAV-loader op drie plekken die we tot nu toe hebben gelogd.

Dus de regel die uit de vorm van de data volgt: bulk AI-geschreven tekst gaat nooit naar productie tot een kopie van productie dezelfde writes heeft opgegeten en een volledige reindex heeft overleefd.

De kopie die de klap opvangt

De read-only staging-vorm bestaat uit drie onderdelen. Geen ervan is slim bedacht.

1. Een bevroren snapshot van de catalogus-tabellen

Maak de snapshot met --single-transaction, zodat de live shop niet gelockt wordt. Je hebt alleen de catalog-EAV-tabellen, de EAV-attribuut-tabellen en de flat-tabellen nodig als flat catalog aanstaat. Sla sales_flat_*, customers en logs over. De dump moet binnen seconden binnen zijn.

mysqldump \
  --single-transaction \
  --skip-lock-tables \
  --no-tablespaces \
  -h db.live.internal -u readonly -p \
  magento_live \
  catalog_product_entity \
  catalog_product_entity_varchar \
  catalog_product_entity_text \
  catalog_product_entity_int \
  catalog_product_flat_1 \
  eav_attribute \
  eav_entity_type \
  > /tmp/catalog-snap.sql

2. Een staging-MySQL die writes standaard weigert

Zet de snapshot op in een MariaDB-container en koppel de credentials die je tooling gebruikt aan een user die alleen SELECT heeft. Houd een tweede user met INSERT en UPDATE achter de hand voor de AI-pour-stap. De applicatielaag krijgt die tweede user nooit te zien.

CREATE USER 'reviewer'@'%' IDENTIFIED BY '...';
GRANT SELECT ON magento_staging.* TO 'reviewer'@'%';

CREATE USER 'pour'@'%' IDENTIFIED BY '...';
GRANT INSERT, UPDATE ON magento_staging.catalog_product_entity_text
  TO 'pour'@'%';
GRANT INSERT, UPDATE ON magento_staging.catalog_product_entity_varchar
  TO 'pour'@'%';
FLUSH PRIVILEGES;

Dit is het deel dat de meeste teams overslaan. Twee users, twee bedoelingen, twee audit trails. Wanneer een junior om 23:00 de AI-pour op de verkeerde host afvuurt, geven de credentials waarover hij beschikt simpelweg geen toegang tot de catalogus van de live shop. Punt.

3. De pour, en daarna een echte reindex

Map de AI-tekst op (entity_id, store_id, attribute_id) voordat je iets wegschrijft. store_id = 0 is de admin- en default-scope; tekst per store view heeft de bijbehorende view-id nodig. Resolve de attribute_id voor description en short_description op naam, niet op een hardcoded integer, want de id's verschillen per installatie.

SELECT attribute_id, attribute_code, backend_type
FROM eav_attribute
WHERE entity_type_id = (
  SELECT entity_type_id
  FROM eav_entity_type
  WHERE entity_type_code = 'catalog_product'
)
AND attribute_code IN ('description', 'short_description');

Draai daarna, alleen op de staging-kopie, de pour binnen één transactie en reindexeer met php shell/indexer.php reindexall. Als de reindex faalt, heeft de catalogus de storefront nooit bereikt en is de staging-instantie de enige blast radius.

De diff die bepaalt wat live gaat

Zodra staging de writes heeft verwerkt, is de vraag niet meer "is de tekst goed". De vraag wordt: "wat is er daadwerkelijk veranderd, en komt de wijziging overeen met wat is goedgekeurd?" Eén diff-query draagt de hele last.

SELECT live.entity_id,
       live.value  AS old_value,
       stage.value AS new_value
FROM live.catalog_product_entity_text live
JOIN stage.catalog_product_entity_text stage
  USING (entity_id, attribute_id, store_id)
WHERE live.value <> stage.value
  AND stage.attribute_id = @desc_attr
ORDER BY live.entity_id;

Exporteer naar CSV, stuur het naar de klant ter goedkeuring en schrijf alleen de goedgekeurde rijen terug naar live met een geparameteriseerde UPDATE ... WHERE entity_id IN (...) binnen één transactie. Hetzelfde pour-script, alleen één flag anders. Geen tweede model-run, geen tweede prompt, geen ruimte voor drift tussen wat is goedgekeurd en wat is weggeschreven.

De uitbetaling op zondagavond

Het bureau uit de 04:12-Loom zette de 8.400 beschrijvingen op zondagavond live. De pour liep vrijdagavond op staging, de diff ging zaterdag om 09:00 naar de klant, hij keurde 7.612 rijen goed en 788 af, en de productie-write was zondag een klusje van vijf minuten, omdat het enige wat moest gebeuren het afspelen van een goedgekeurde diff was. Geen nieuwe prompts. Geen "kunnen we de toon nog even bijschaven". Dat was al beklonken op staging.

Het punt van de read-only staging-vorm is niet om het werk te vertragen. Het punt is om het onomkeerbare deel (writes tegen een EAV-catalogus uit 2014 met een live boekhoudkoppeling eraan vast) te verplaatsen naar een plek waar onomkeerbaarheid niets kost.

Toen we Pier bouwden, de FTP- en MySQL-editor die we voor werk aan verouderde sites gebruiken, was dit precies het patroon waarom de database-kant wordt geleverd met twee connection-modi en een versiegeschiedenis op rijniveau bij elke wijziging. De MySQL-editor kiest standaard staging wanneer beide hosts zijn geconfigureerd, en de switch naar productie is een bewuste toetsaanslag, geen onbedoelde. De vorm is de veiligheid.

Het kleinste dat je vandaag kunt doen: open je staging-MySQL, draai SHOW GRANTS FOR CURRENT_USER();, en controleer of de user waarmee je AI-pour-script verbindt UPDATE-rechten heeft op de live database. Zo ja, dan is dat morgenochtend je eerste config-wijziging.

— Vragen —

Mag ik staging overslaan als ik maar 50 beschrijvingen hoef te herschrijven?

Het risico heeft dezelfde vorm, alleen kleiner. Een staging-kopie heb je in tien minuten staan en maakt van een onomkeerbare write een omkeerbare. Doe het ook bij 50 rijen.

Vangt een reindex op staging echt op wat productie zou opvangen?

Ja, mits de snapshot de EAV-attribuut- en flat-tabellen bevat en het aantal store views matcht met live. Een mismatch in store-view-aantallen is meestal de reden dat staging slaagt en productie alsnog faalt.

Werkt dit patroon ook op Magento 2?

Zelfde vorm, andere tabelnamen. Magento 2 gebruikt catalog_product_entity_text en catalog_product_entity_varchar met EAV-value-tabellen in plaats van standaard flat-tabellen. De regel met twee staging-users blijft overeind.