Add damrs_sync, and fix the data loss it exposed

Contribution Date
Contribution Project
Contribution Details
Add damrs_sync, and fix the data loss it exposed A signed endpoint, a queue, and a worker that applies damrs events to the media items referencing an asset. This closes the gap damrs_media left deliberately: Drupal re-reads metadata only when a mapped field is empty or the source field changed, so an asset retitled in the DAM would otherwise never update on the site. **Two correct modules destroyed data together.** damrs_media falls back to the value already in a mapped field when damrs cannot answer, so an outage cannot blank cached metadata. Refreshing works by clearing those fields so Drupal's own "field is empty, read it" branch runs — which removes the very value the fallback would have returned. A refresh event arriving during an outage therefore erased the metadata it was meant to update. Both suites were green; the live check showed a title going to NULL. Fixing it needed a distinction the client could not express. asset() returns NULL both for "damrs did not answer" and for "damrs says there is no such asset", and for anything deciding whether to retry those are opposites: retrying everything makes a deleted asset an item that never drains, and retrying nothing makes a one-minute outage erase what it could not refresh. So ApiResult carries the status, nothing is cleared until damrs has actually produced the asset, an unreachable damrs suspends the queue run so the items stay queued, and a deletion never asks at all. **The verifier is pinned to forgeries, not to the happy path.** The endpoint has no access requirement — damrs has no session and no form token — so the HMAC is the entire boundary, and a verifier wrong in the accepting direction is an endpoint anybody can post content changes to. That is a different severity from the delivery tokens, where a mistake only stops images rendering. The vectors carry the forgeries a plausible implementation accepts, including a correct digest over the body *without* the timestamp: it passes every happy-path test and makes every signature a permanent replay token. Mutation-tested four ways. Signing the body alone, dropping the freshness window and coercing the timestamp are all caught. Replacing hash_equals with === is not, and provably cannot be — only the timing differs — so that one is guarded by a structural assertion which says outright what it is rather than leaving a silent gap. Driven end to end against the live endpoint with deliveries signed by the Rust signer: valid is 202, and missing, wrong-secret, tampered-body and stale are each 401. 17 kernel tests, 32 unit tests, phpcs clean.
Contribution Author
Bassam Ismail
Files count
0
Patches count
1