Content, examples and notification-channel findings from the pre-beta audit
Part of #3620619, covering the content, examples, notification-channel and
maintainer-script findings. The rest of the issue is a separate branch, so
nothing here touches src/ or the other submodules. Two files outside
modules/ do change: translations/untranslated.txt gains the one log message
added here, and tests/src/Unit/MetricsWriteTest.php is new.
A commit per finding. Every behaviour change carries a test that fails without it; the documentation-only ones do not.
orchestra_content_eca
The shipped example ECA model declared a plain config dependency on the workflow it starts. ECA recalculates an ECA config entity's dependencies from nothing on every save, putting back only the modules providing the plugins it holds and what its events name in entity types and fields, so that dependency lasted until the first save from the modeller, and deleting the workflow then left the model behind starting nothing. It is enforced now, which the install gate reads the same way, so the example still waits for the workflow to exist and still goes when it goes.
The README names the example and the four modules it spans, since it is
optional configuration and so is the workflow it starts: eca_content for the
entity event, this module for the action, orchestra_content_moderation for
the workflow, and orchestra_interaction_task for the review step inside it.
orchestra_content
The README omitted orchestra_interaction, a hard dependency, and said
orchestra_content_moderation starts a process on create or update. It does
not: that is orchestra_content_eca, from an ECA model.
orchestra_content_moderation
It ships example_content_review and said nothing about it, so the workflow
either appeared unexplained or did not appear with nothing to read about why.
orchestra_examples
The cron responder never cleared the state key the async responder stashes the waiting task in, so once the round-trip finished the key named a consumed token and every later cron run asked for it to be resumed. It clears the key after answering, and a condition on the key stops the model before the resume action when nothing is waiting, so the example no longer asks the engine for something it knows is not there.
orchestra_inbox_notification
onTaskTimeout() was the one notification handler with no guard. The timeout
event is dispatched from inside the sweep's fire transaction, which commits the
timeout action and the timer's bookkeeping together, so a throw rolled that
back and the sweep pushed the deadline forward before retrying: the
notification layer decided when the task was asked about again. Not a mail
server being down, which neither shipped channel lets out; what throws is
resolving the audience or the label, and a subscriber of a site's own that does
not guard its send. Those are deterministic, so retrying them hourly for ever
is the worse of the two outcomes. Guarded and logged, like the assignment path
and the arrival path, and the README says a reminder can be dropped that way.
orchestra_easy_email
The README and a docblock both promised [user:*] resolved per recipient. Easy
Email only reaches its per-recipient rendering for a template carrying one of
its own one-time-URL tokens; on every other template those tokens were left
with nothing. The channel already knows who it is addressing, so it answers
them itself whenever the message has a single reader, which is every message it
sends one per recipient: with no Cc or Bcc, and with Bcc alone, since blind
copies go out individually. Only a message shared under To and Cc has no single
account, and the README now describes all three groupings rather than two.
Only a user entity is passed on: a recipient carries an AccountInterface, and
AudienceBase::convertAccountsToRecipients() lets an audience of a site's own
hand over a session, which the user module's token replacements would call
entity methods on, costing the whole notification rather than one empty token.
drupal:user is declared now, since the channel resolves that module's tokens
on purpose.
orchestra_mail
The generic key looked for a composed subject and body that nothing in
Orchestra sets and no node has a field for, and ignored the message param
every other key honours, so the one type meant for a site's own words said the
same sentence as an unrecognized one. It reads the label and the message now,
each falling back to the neutral wording. A Notify node sends neither, so that
is what it mails, and the README says so instead of promising author-composed
content.
scripts/generate-metrics.php
The generator printed "Wrote docs/metrics.md" and exited zero whether or not the write landed, over a page that still parsed as Markdown either way.
It had nowhere to put a test for that, being a single file of functions with an
entry point at the bottom, so requiring it ran it. check-translations already
solved this here: a runner over an .inc, split because the .inc is the half
that can be wrong quietly. The generator has the same shape now, which is most
of the diff and moves no code otherwise: the moved text is identical line for
line, and the two scripts generate a byte-identical page. The writing reaches a
verdict and hands it back rather than announcing one, and MetricsWriteTest
asserts that verdict, not what file_put_contents() does.