B5 and B6 — the first workflow registers, and the walkthrough leads somewhere - #276
Open
gplanchat wants to merge 2 commits into
Open
B5 and B6 — the first workflow registers, and the walkthrough leads somewhere#276gplanchat wants to merge 2 commits into
gplanchat wants to merge 2 commits into
Conversation
…elque part Deux défauts du même chemin, celui que suit quelqu'un qui découvre Durable. **Le premier workflow ne s'enregistrait pas.** Le guide posait `#[AsActivity]` sur la classe d'implémentation. Or cet attribut est lu par `ActivityContractResolver` sur le **contrat**, comme préfixe de nommage optionnel — sur une implémentation, personne ne le lit. Et il y remplaçait `#[AsActivityHandler]`, le seul des deux que le bundle autoconfigure. Le lecteur suivait le guide à la lettre et rien ne se branchait. Corrigé dans les quatre pages qui portaient l'erreur, deux langues comprises. La page des activités annonçait la même chose en toutes lettres — « souvent annotée d'un `#[AsActivity]` pour son nom » — et la page des paquets promettait que `#[AsWorkflow]` et `#[AsActivity]` s'enregistrent seuls, ce qui est faux des deux : le premier n'est pas autoconfiguré à ce jour, le second n'est pas un attribut d'enregistrement. **Le parcours ne menait à aucun résultat.** Il s'arrêtait sur un `dispatchNewWorkflowRun()` qui rend `void`, sans dire qu'un consommateur doit tourner ni lequel. Une étape 5 le dit, avec la commande — dont les noms de transports viennent du `messenger.yaml` que le guide fait écrire, ce qui explique qu'aucun document ne puisse la donner sans ça. Et elle distingue les deux profils que le guide mélangeait : `in-memory://` avec des magasins en mémoire vaut pour un test qui envoie et draine dans un seul processus, mais un transport en mémoire ne survit pas à son processus — envoyer depuis une requête web pour consommer dans un worker séparé ne peut pas marcher, et le rejeu non plus. Le profil multi-processus demande de vrais transports **et** un magasin durable, les deux, sinon le worker prend une entrée nommant un workflow dont il ne voit pas le journal. Refs: B5 et B6 de documentation/audit/ Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r ne boucle plus La relecture croisée a montré que B5 n'était fermé qu'à moitié. L'attribut fautif est bien corrigé partout — `#[AsActivityHandler]` sur les implémentations, `#[AsActivity]` sur les seuls contrats — mais le guide fait baliser `../src/Workflow/` sans `exclude` et range ses activités dessous, en `App\Workflow\Activity`. `WorkflowPass` ne filtre rien d'autre qu'un antislash dans le nom de classe, `WorkflowRegistry::registerClass()` appelle le chargeur sans garde, et `WorkflowDefinitionLoader:144` lève « must have exactly one #[AsWorkflowMethod], found 0 ». Le lecteur qui suivait le guide à la lettre obtenait toujours un conteneur qui ne se construit pas — pour une autre raison, dans le paragraphe voisin de celui que cette branche réécrivait. Le banc du dépôt (`symfony/config/services.yaml:23-29`) montrait déjà la forme qui marche : au dossier balisé, ses seuls workflows. Le routage publié envoyait `FireWorkflowTimersMessage` en `sync`. Le minuteur est alors rejoué dans le processus qui vient de le programmer. Le banc porte exactement cette ligne dans sa configuration de base — et l'écrase dans `when@dev`, `when@prod` et `when@test`, avec la raison en commentaire : « les réveils timer portent DelayStamp ; sync:// les exécute tout de suite et ignore le délai — les workflows sample (Query 2s, Periodic 0.2s) ne terminent jamais ». Le guide avait copié la ligne sans la surcharge. Il route désormais vers `durable_workflows`. `activity_contracts` nommait `App\Workflow\Activity\OrderActivities`, que le guide ne crée jamais : `cache:warmup` mourait sur une `ReflectionException` avant le premier workflow. C'est `GreetingActivities`, le contrat des étapes suivantes. Le contrôleur d'exemple répondait `200` à un envoi asynchrone qui n'a rien exécuté. `202`. Le profil « plusieurs processus » nommait `doctrine.dbal.default_connection` sans jamais faire installer ni le pont DBAL ni DoctrineBundle. La commande manquante est là. La parité ligne à ligne des deux langues est conservée : les seize titres restent aux mêmes numéros de ligne dans `_index.md` et `_index.fr.md`. Reste ouvert, hors périmètre : un banc qui suive le guide et rougisse quand il cesse de marcher. Le dépôt en a la machine — le job « Module Magento (il démarre pour de vrai) » va jusqu'à « Un workflow tourne dedans ». C'est un job de plus, pas une architecture, et c'est ce qui aurait fait de cette branche une régression rouge plutôt qu'une relecture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Documentation only, six files, two languages. No code.
B5 — the guide's first workflow did not register
The guide put
#[AsActivity(name: …)]on the implementation class. Checked against the code: that attribute is read byActivityContractResolver::resolveViaReflection()on the contract, as an optional naming prefix. On an implementation, nobody reads it.Worse, it took the place of
#[AsActivityHandler(contract: …)]— the only one of the two thatDurableBundle::build()autoconfigures. Readers followed the guide to the letter and their class was never registered on the activity executor.The real model, now the guide's:
#[AsActivity(name:)]#[AsActivityMethod(name:)]#[AsActivityHandler(contract:)]Three pages carried the same error, and the activities page spelled it out: "a concrete class (often annotated with
#[AsActivity]for its name)". Fixed in both languages.The packages page also promised that "classes carrying
#[AsWorkflow]and#[AsActivity]register themselves". That is wrong for both:#[AsWorkflow]is not autoconfigured to date (that is the subject of #255) and#[AsActivity]is not a registration attribute. The page now states the real situation, and points at thedurable.workflowtag the guide actually has you write.B6 — the walkthrough led to no result
The guide stopped at a
dispatchNewWorkflowRun()that returnsvoid. Nothing said a consumer had to be running, nor which one — and the only section that mentioned workers was conditioned on Temporal, hence absent from the path the guide prescribes by default.A step 5 now says it, with the command:
Those two names are the transports the reader has just declared in their own
messenger.yaml— which is why no document can hand you this command without that file existing first, and why one looks for the missing piece everywhere except in one's own configuration.The two profiles the guide was mixing
This is the substance of B6, and it was written nowhere. The guide prescribed
in-memory://"for tests and local dev", then showed a dispatch from a controller. The two together cannot work:The configuration given for the second profile is not invented:
event_store.type,workflow_metadata.typeandchild_workflow.parent_link_store.typedo acceptdbal, checked inConfiguration.phpbefore being written.Neighbourhood
Step 5 answers #263 ("nothing says a worker must run, nor which queues it consumes") on the documentation side; the issue also asks for a first-class
durable:workercommand, which remains to be done. The profile distinction states what #259 observes from another angle: routing durable work where a separate worker cannot read it makes the workflow replay inside the request that started it.Verification
Final check across the six files: no implementation carries
#[AsActivity]any more, each carries#[AsActivityHandler], and every remaining#[AsActivity]is on an interface.Addendum — 2026-09-04, after cross-review
B5 was only half closed. The offending attribute is indeed fixed everywhere — that was verified. But the guide had readers tag all of
../src/Workflow/without anexclude, and placed the activities underneath it, inApp\Workflow\Activity.WorkflowPassfilters on nothing but a backslash in the class name,WorkflowRegistry::registerClass()calls the loader without a guard, andWorkflowDefinitionLoader:144throws "must have exactly one #[AsWorkflowMethod], found 0". A reader following the guide to the letter still ended up with a container that would not build — for a different reason, in the paragraph next to the one this branch was rewriting. The repository's own test bench (symfony/config/services.yaml:23-29) already showed the shape that works: the tagged folder holds workflows and nothing else.The published routing sent
FireWorkflowTimersMessagetosync. The timer is then replayed inside the very process that scheduled it. The bench carries that exact line in its base configuration — and overrides it inwhen@dev,when@prodandwhen@test, with the reason in a comment: timer wake-ups carry aDelayStamp,sync://runs them immediately and ignores the delay, so the sample workflows never finish. The guide had copied the line without the override. It now routes todurable_workflows.activity_contractsnamedApp\Workflow\Activity\OrderActivities, which the guide never creates:cache:warmupdied on aReflectionExceptionbefore the first workflow. It isGreetingActivities, the contract of the following steps.The example controller answered
200to an asynchronous dispatch that had executed nothing. It answers202.The "several processes" profile named
doctrine.dbal.default_connectionwithout ever having you install either the DBAL bridge or DoctrineBundle. The missing command is there.Line-by-line parity between the two languages is preserved: the sixteen headings remain on the same line numbers in
_index.mdand_index.fr.md.Still open, out of scope: a bench that follows the guide and goes red when it stops working. The repository has the machinery — the "Magento module (it actually boots)" job goes as far as "a workflow runs in it". That is one more job, not an architecture, and it is what would have made this branch a red regression rather than a review finding.