diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51644832..3a012f8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ Every change that reaches `main` adds a line under **Unreleased**. When a versio
### Tooling
- The Symfony NEXT CI leg keeps PHPUnit and ParaTest on their stable releases, so it tests the next Symfony rather than unreleased test tools ([#584](https://github.com/components-web-app/api-components-bundle/pull/584))
+- The component position tests' set-up backdates the positions' `modifiedAt`, so a write under test always changes a stored value on Doctrine ORM 3.8, which skips an update whose only change is a `datetime` in the same second ([#586](https://github.com/components-web-app/api-components-bundle/pull/586))
## [2.0.0-alpha.40](https://github.com/components-web-app/api-components-bundle/compare/2.0.0-alpha.39...2.0.0-alpha.40) - 2026-10-31
diff --git a/CLAUDE.md b/CLAUDE.md
index 994d1824..2d0c7fbc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -94,6 +94,7 @@ API behaviour gets a functional test in `tests/Functional//Test.php
- **The test app runs the Varnish xkey purger (space-joined), production runs Souin (`', '`, chunked at 1500).** Never assert an exact purge or tag header; use `CacheTagsTrait::purgedCacheTags()` / `responseCacheTags()` and their assertions, which split on `/[,\s]+/`.
- **The test app imports routes with no prefix.** A test that needs `/_api` boots the kernel with `routePrefix`, which gets its own cache directory and container class.
- **Test doubles with static switches** (`HubStub`, `MockClientCallback`, `UnreachableDatabaseMiddleware`, `RejectedDeleteMiddleware`, `QueryCountingMiddleware`, `BeforeTransactionCallback`, `SwitchableMailer`, `KernelHttpClient`) are reset before and after every test by `FunctionalTestCase::resetTestDoubles()`, with the `EMAIL_LINK_DEFAULT_ORIGIN` and `ORPHANED_RESOURCES_NOTIFY_RECIPIENTS` env vars; a new switch goes there. Static because the kernel can reboot between requests. `QueryCountingMiddleware::count()` is how a functional test pins a query count; the profiler collects no Doctrine queries with `debug: false`. Logs are asserted through the `app.monolog.test_handler` Monolog `TestHandler`, because the harness runs with `debug: false` and the profiler's logger collects nothing.
+- **From Doctrine ORM 3.8 a write whose only change is `modifiedAt` in the same second as the stored value is not an update** (doctrine/orm#12753 compares database values, and `modifiedAt` is a `datetime`): nothing is flushed, purged or published. A test that expects a write to propagate a resource whose other fields keep their values must set it up with an earlier `modifiedAt`, as `ComponentSiteTrait::createGroupWithPositions()` does.
- **Newly covering a large declarative file with a unit test costs MSI**, because its mutants start counting (functional coverage does not count). Answer with assertions on the wiring, not exclusions.
- **Every CI test step runs `vendor/bin/paratest`** over `phpunit/phpunit` from Composer, `--processes="$TEST_PROCESSES"` (4, a runner's vCPUs), after warming each worker's cache, **except the coverage leg, which runs `vendor/bin/phpunit` in one process** for Codecov. Keep `brianium/paratest` on a version that supports the installed PHPUnit major.
- **PHPStan baseline:** 65 findings, none of them `method.notFound` (#324 worked through all 23). Fixing a baselined finding means regenerating with `vendor/bin/phpstan analyse --memory-limit=1G --generate-baseline phpstan-baseline.neon`; a stale entry fails the job. Narrow with `instanceof` or a precise parameter type, never a `@var` override. `src/Resources/skeleton` is excluded.
diff --git a/tests/Functional/Component/ComponentSiteTrait.php b/tests/Functional/Component/ComponentSiteTrait.php
index 9a9f1ab1..0fec40c9 100644
--- a/tests/Functional/Component/ComponentSiteTrait.php
+++ b/tests/Functional/Component/ComponentSiteTrait.php
@@ -70,6 +70,12 @@ protected static function createGroupWithPositions(string $positions, string $re
array_push($entities, $component, $position);
}
static::persistAndFlush(...$entities);
+ static::getEntityManager()
+ ->createQuery(\sprintf('UPDATE %s p SET p.modifiedAt = :earlier WHERE p.componentGroup = :group', ComponentPosition::class))
+ ->setParameter('earlier', new \DateTime('-1 hour'))
+ ->setParameter('group', $group)
+ ->execute();
+ static::rebootKernel();
$byValue = [];
foreach ($created as [, $position]) {