Skip to content

feat: adopt ai-translation-bundle 1.4 - #8

Merged
JDCyllene merged 9 commits into
CylleneDigital:mainfrom
JDCyllene:feat/adopt-bundle-1.4
Oct 16, 2026
Merged

JDCyllene merged 9 commits into
CylleneDigital:mainfrom
JDCyllene:feat/adopt-bundle-1.4

Conversation

@JDCyllene

@JDCyllene JDCyllene commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

What this pull request does

Adopts what cyllene-digital/ai-translation-bundle 1.2.0 to 1.4.1 bring to the plugin, and fixes what the upgrade revealed. No migration, no mapping change.

  • The constraint goes to ^1.4.1: the README, the installation guide, the support policy, CONTRIBUTING.md and the comment of the Oldest supported engine job follow.
  • A hand edit reads the value its entry inherits once, with OverrideReader::getInheritedValue(): it gives both the baseline and, on a revert, where the entry goes back to. A revert no longer reads the whole catalogue.
  • The catalogue screen reads the overrides of every catalogue of the view at once (OverrideReader::getTranslationsForCatalogues()): two queries for the browsed locale, whatever the number of catalogues, where each catalogue took two of its own.
  • The channel scope provider declares the locales of each channel (ScopeLocalesProviderInterface): an import now skips, and names in its preview, an entry in a locale its channel does not offer. Stored, it would never be served nor shown. A parent language is offered (fr for a channel in fr_FR); a channel without a locale restricts nothing.
  • A run whose channel is deleted while it runs now stops before its next chunk. The engine checks the channel when a run starts and before each chunk, but the plugin kept its list of channels for the whole Messenger message, so a channel deleted during a run went unseen and every next chunk was billed. Outside a request, ChannelRegistry now keeps the channels one second at most; within a request, nothing changes.
  • A row the engine reserved before calling the provider is no longer shown as a failure. Since 1.2.0, the keys of a chunk are stored as errored suggestions while the provider works: the grid showed them as "Generation failed" during every run. They get a grey "Sent to the provider" badge, and the review modal a notice in place of the failure; the "Generation failed" filter and its help keep them.
  • A deleted channel takes what the engine stores for it along (ScopePurger, 1.3.0): once Sylius has committed the deletion, on sylius.channel.post_delete after the host's own listeners, its overrides, suggestions and prompt context are purged; the generation journal stays. A purge that fails is logged and the deletion goes through; cyllene:ai-translation:purge-scopes catches what it left, and what a channel deleted another way (a script, Sylius' API, SQL) keeps. Record 0025 supersedes 0023.
  • Record 0024 supersedes 0022: the guard against double billing is now written into the engine's public contract, which the plugin relies on instead of a behaviour it did not promise. It also records why the channels are kept only briefly outside a request.
  • The docs say what changes for a host: the engine's translation_files_cache pool and when to empty it, what a run refused or stopped for a deleted channel leaves, keeping the engine's transport asynchronous, and what a replaced or decorated scope provider must implement.

The unit TranslationViewFactoryTest goes: it stubbed the manager interface, and the grouped read is a method of OverrideReader, a final class no unit test can build. tests/Integration/View/TranslationViewFactoryTest.php counts the queries of a view on the real engine instead (Doctrine's debug middleware), and fails on a return to the per-catalogue read (13 queries instead of 3 on the test application). CatalogueRowsTest checks that each catalogue keeps its own overrides and inherited values.

Left as they are:

How it was checked, on Sylius 2.3 and PHP 8.4 against MariaDB: composer validate --strict, lint:container, lint:twig, lint:yaml, doctrine:schema:validate, PHPStan and ECS, PHPUnit (152 tests) and the 46 Behat scenarios, @javascript included. Each new test was checked to fail without the change it covers: the import test without ScopeLocalesProviderInterface, the in-flight tests with the templates and the Stimulus controller as they were, the query count test with the per-catalogue read, the channel deletion tests with the listener unwired or at the default priority.

Also tried on a development shop with a real provider and worker: a channel deleted during a 15-chunk run stops it before its next chunk; a worker killed during a call leaves "Sent to the provider" rows that "Retry errors" fills; an import line in a locale its channel does not offer is skipped; deleting a channel from the admin purges its overrides and suggestions and keeps its journal. On a synthetic catalogue of 5,026 keys in 26 catalogues, the full view costs 3 queries instead of 53.

For a host upgrading

Nothing to change: UPGRADE.md lists breaks only, and nothing of the public surface breaks. Worth knowing:

  • deleting a channel now deletes its overrides and suggestions, which did not come back before either: export the channel's view first to keep a copy. Whoever may delete a channel in Sylius may do it, with no role of the plugin;
  • the channels deleted with 1.0.x left their rows: bin/console cyllene:ai-translation:purge-scopes lists them, --force purges them;
  • a theme overriding admin/suggestion/content/review_modal.html.twig keeps working; adding the inFlight target block of the plugin's template gives it the "Sent to the provider" notice;
  • the catalogue screen reads the browsed locale's overrides from OverrideReader, no longer through TranslationManagerInterface::getTranslationsForCatalogue(): a host decorating that method no longer sees this screen go through it.

Points to watch

  • Tests pass (vendor/bin/phpunit, vendor/bin/behat --strict)
  • PHPStan and ECS are green
  • UPGRADE.md is up to date if the public surface changes (routes, Twig hooks, templates, role, menu entry, services aliased over the engine) - nothing breaks: the channel scope provider implements one more engine interface, which docs/architecture/public-contract.md now names for a host replacing or decorating it. admin/suggestion/grid/field/suggested.html.twig, admin/suggestion/grid/_review_trigger.html.twig and admin/suggestion/content/review_modal.html.twig change: a theme that overrides them keeps working, without the "Sent to the provider" badge and notice
  • A decision taken or reversed has its record in docs/adr/ - 0024, superseding 0022, and 0025, superseding 0023
  • No API key, customer data or pre-production URL appears in the code, the tests or this description

If the pull request needs a newer engine

cyllene-digital/ai-translation-bundle 1.4.1: getInheritedValue() and the guarantees of a generation run are in its contract since 1.2.0, getTranslationsForCatalogues() and ScopeLocalesProviderInterface since 1.3.0, the cache pool since 1.4.0, and 1.4.1 fixes the events of concurrent writes. The constraint is raised in composer.json.

- require ^1.4.1;
- read the inherited value of a reverted hand edit with getInheritedValue();
- read the overrides of every catalogue of a view at once (getTranslationsForCatalogues());
- declare the locales of each channel (ScopeLocalesProviderInterface): an import skips an entry
  in a locale its channel does not offer;
- record 0024, superseding 0022: the guard against double billing is now in the engine's
  contract;
- document the translation files cache pool and the refusal of a run for a deleted channel.
@JDCyllene
JDCyllene requested a review from a team as a code owner October 15, 2026 15:04
…m a failure

Found reviewing the adoption of the bundle 1.4:

- outside a request, the channels are kept one second at most: the engine checks the channel
  of a run before each chunk, and a list kept for the whole Messenger message hid a channel
  deleted during the run;
- a row the engine reserved before calling the provider shows a "Sent to the provider" badge
  instead of "Generation failed", and no failure in the review modal;
- a hand edit reads the inherited value once;
- the cost of a view is counted again, on the real engine: the number of catalogues does not
  change it;
- record 0024 and the docs say what a refused run leaves, when the parsed files pool needs
  emptying, and what a replaced or decorated scope provider must implement.
Found reviewing the previous fix:

- the review modal shows a "Sent to the provider" notice for a reserved row, where it showed
  nothing; the row keeps the engine's error for when its run was killed;
- the "Generation failed" filter help says it keeps the reserved rows too;
- the channel list computes its expiry once;
- the query count of a view is measured after a warm-up call;
- record 0024 no longer copies the engine's guarantees, and names its cost and the synchronous
  transport; the docs word the parsed files pool, a parent language at an import and an
  unguarded run as the engine does.
- a reserved row is also settled by hand in the review modal, not only by "Retry errors";
- record 0024 says the channel is checked when a run starts too;
- the filter help keeps to what the filter keeps;
- the review modal's in-flight notice is checked by the functional test, and the query count
  test loses a warm-up call that warmed nothing.
Once Sylius has committed a channel's deletion, the engine's ScopePurger
removes its overrides, suggestions and prompt context; the generation
journal stays. A purge that fails is logged, and purge-scopes catches it
later, with what channels deleted another way left behind. ADR 0025
supersedes 0023.
…decision

- The purge runs after the host's post_delete listeners, catches the
  failures of a purge only (an \Exception, not an \Error) and logs them
  neutrally; a test deletes a channel through Sylius' grid.
- The review modal works with an overridden template that lacks the
  in-flight target; a reserved row's approve button says its value is
  still to come, and its help that a decision taken now stands.
- A Behat scenario opens the review of a reserved row.
- The docs say what a bulk deletion, Sylius' API, a large channel and a
  synchronous transport do, and ADR 0025 says who may delete a channel.
… as they behave

- The docs say that a purge failing during its flush in a bulk deletion
  ends on an error page, that a channel created again under a code finds
  what was left under it, and that deleting a channel needs no role of
  the plugin.
- Without the in-flight target, the review modal shows the row's error
  as before; a test pins the purge after the host's listeners.
- The approval flashes name a row still with the provider.
… reading their priority

Symfony 7.4 and 8 type getListenerPriority() differently, which PHPStan
refuses on one or the other: a probe listener at -1000 runs before the
purge, which must come last.
@JDCyllene
JDCyllene merged commit 03f7417 into CylleneDigital:main Oct 16, 2026
18 checks passed
@JDCyllene
JDCyllene deleted the feat/adopt-bundle-1.4 branch October 16, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant