# Dispatch uncertain-outcome reconciliation

Audience: AI coding agents first.

## Outcome

Prevent duplicate translation delivery when backend submission outcome or local backend-job-ID persistence is ambiguous. Keep affected local jobs in explicit reconciliation state. Reconcile through stable client job ID before any new delivery.

## Status

IDLE — bounded reconciliation terminal-state blocker fixed and locally verified; local commit pending. Separate callback WIP remains preserved and uncommitted.

## Source request

Fix review blockers in `includes/Translation/TranslationJobDispatcher.php`: transport errors can happen after backend accepted `clientJobId`; persistence can fail after acceptance. Do not mark jobs failed and dispatchable. Fix automated security findings, especially safe dispatcher errors and exclusive random migration fixture. Follow-up correction: an accepted request with a lost response must reconcile and finalize autonomously through bounded durable scheduling/processing; it must never require a second manual dispatch. Idempotency keys must be globally site-scoped, and backend creation plus lookup must enforce identical authenticated site/plugin/user scope so shared accounts cannot collide. Run focused PHP/backend tests, PHPCS/PHPStan, TypeScript/Jest, and race tests. Commit locally; do not push.

## Acceptance criteria

1. Transport ambiguity never moves accepted-or-possibly-accepted work to immediately dispatchable failed state.
2. API-job-ID persistence failure retains explicit uncertain/reconciliation state.
3. Every uncertain accepted job schedules exactly one durable bounded reconciliation worker, which obtains the backend identity without manual dispatch; retries neither create nor deliver duplicate jobs.
4. An authenticated callback atomically binds its backend ID to a matching reconciling local job before finalization.
5. Every new content and bulk-string client job ID is globally site-scoped; backend creation and lookup enforce the same authenticated site, plugin, and user scope.
6. Dispatcher error handling is safe for untrusted transport/backend error content.
7. Migration test fixture owns an exclusive randomized resource.
8. Focused PHP tests, PHPStan, PHPCS, backend TypeScript/Jest, and race tests pass without unresolved warnings.
9. Work is locally committed; no push.
10. After five inconclusive backend lookups, persist a terminal manual-review outcome. Keep it non-dispatchable until controlled recovery clears the duplicate-acceptance risk.

## Preserved WIP/ref/path

Worktree: `/home/user/Projects/Press.zone/wordpress/wp-content/.worktrees/acf-task11-runtime`.

Preserve all unrelated existing worktree state. Touch only requested dispatcher, focused tests/fixtures, and this plan/index receipt.

## Constraints

- Use local `devzone-wordpress` fixture for WordPress tests.
- No production or `dev1` deployment requested.
- Keep idempotency keyed by stable client job ID.
- Do not suppress fixable warnings.

## Execution steps

1. Inspect dispatcher state machine, existing WordPress scheduling patterns, callback binding, backend schema/API scope, focused tests, migration fixtures, and current security findings.
2. Add one bounded durable reconciliation schedule per local job and an idempotent worker that reconciles without a manual redispatch.
3. Bind a signed callback's backend identity to its matching reconciling local job atomically before finalization.
4. Replace local-ID-only client keys with site-scoped identities in every producer and consumer.
5. Persist authenticated site identity on backend jobs; scope creation deduplication and lookup to the same authenticated site/plugin/user tuple with a migration.
6. Cover autonomous recovery, duplicate-schedule avoidance, callback recovery, and cross-site lookup isolation; run requested gates and resolve every signal.
7. Update this receipt; commit locally without pushing.

## Current receipt

2026-08-13: Added a per-job WordPress scheduled reconciliation hook. Every ambiguous submission schedules one delayed worker; duplicate scheduling is suppressed by hook plus job arguments. The worker uses a per-job advisory lock, checks authoritative `reconciling` state, retries transient lookup failures up to five times, and never re-dispatches backend delivery. Backend `GET /v1/jobs?clientJobId=` lookup now also requires authenticated plugin in addition to user and site, matching creation/deduplication scope; the migration index matches that tuple. Focused dispatcher recovery test, dispatcher PHPStan/PHPCS, backend Jest and build, and terminal race pass. `TranslateJobsCompletionStandaloneTest.php` fails before this work in current HEAD: it asserts that `StringTranslateController::bulkCallback()` delegates to the content-job callback handler, while the unchanged bulk-string callback has a distinct payload contract. Its changed test/controller pair is unrelated staged WIP and is intentionally preserved, uncommitted. The MariaDB ACF race fixture requires its isolated WordPress/Podman harness; direct execution within `devzone-wordpress` lacks WordPress bootstrap, so it is not evidence.

2026-08-13 follow-up: Fixed max-attempt exhaustion. Fifth inconclusive lookup now atomically moves `reconciling` to `failed`, retains `retry_count = 5`, records explicit manual-review guidance, and stops autonomous scheduling. Dispatcher active-job lookup still treats this specific exhausted row as non-dispatchable, so ordinary submission cannot duplicate a possibly accepted backend job; controlled recovery/manual review can identify it by durable status, retry count, and message. Focused standalone regression proves terminal persistence, no sixth schedule, no backend redispatch, and visible blocked state. Scoped PHPCS, PHPStan, standalone regression, and `git diff --check` pass without warnings. Unrelated `TranslateJobsController.php` and `TranslateJobsCompletionStandaloneTest.php` changes remain untouched.

## Next executable action

Commit only dispatcher, focused standalone regression, and this receipt. Do not push.
