# ACF Translation Modes — request

Audience: AI coding agents first.

**Goal:** Restore `Translations → Fields` and deliver matrix-compatible global/per-field Auto, Manual, and Mirror origin behavior end-to-end, including locks, source propagation, transition reconciliation, verification, release, and dev1 deployment.

**Canonical design:** `docs/specs/2026-08-09-acf-never-translate-design.md`. Every contract and acceptance rule in that file is mandatory. If repository reality conflicts with a named implementation seam, preserve behavior/invariants and document the minimal seam adjustment; do not weaken the matrix.

## Context

Current repository retains ACF graph traversal in `includes/Compatibility/ACFIntegration.php`, but the historical Fields page/controller were removed after `b140d1fe` and before/current removal commit `f6e87990`. Existing modes are `translate|copy|ignore`; current source saves do not propagate ACF changes, Auto manual edits have no target locks, Manual changes do not mark targets stale, and `copy` updates only during translation apply.

Historical sources to adapt, never wholesale-revert:

- pre-`f6e87990`: `admin/src/pages/fields-translate.js`
- pre-`f6e87990`: `includes/API/ACFFieldsController.php`
- related historical Fields-page SCSS and controller tests

Repository default branch is `master`. Preserve all concurrent/current routing, CPT handling, admin behavior, and translation functionality.

## Files

Create only when required by canonical design; match repository namespaces/autoloading:

- `includes/Compatibility/ACFFieldPolicy.php` — canonical/legacy policy resolution, persistent generations, transition-aware active/desired state.
- `includes/Compatibility/ACFMutationLock.php` — sorted top-level storage-root serialization.
- `includes/Translation/ACFSourceChangeCoordinator.php` — final source-save revision detection, Mirror propagation, status/dispatch coordination.
- `includes/Translation/TranslationJobDispatcher.php` — production async content dispatch seam shared by REST and source coordinator.
- `includes/API/ACFFieldsController.php` — Fields configuration, polling, retry, and abort trust boundary.
- migration/schema files required for durable policy transition registry and hardened queue lifecycle, following existing database migration patterns.
- focused unit/integration fixtures required by Acceptance.

Modify:

- `includes/Compatibility/ACFIntegration.php` — purpose-specific graphs, exact snapshots, raw canonicalization, Auto/native locks, Mirror guards, field discovery/settings.
- `includes/API/TranslateController.php` — delegate production dispatch; exact force-overwrite request contract.
- `includes/API/TranslationsController.php` — Manual preview/apply and changed-Auto lock creation.
- `includes/Translation/TranslationFinalizer.php` — final mutation revalidation, locked preservation/force replacement, Mirror sync, exact compensation.
- `includes/Translation/TranslationBridge.php` — source/target resolution seams only; no ACF policy duplication.
- `includes/Core/ContentManager.php` and translation status persistence — separate core/owner revisions and unresolved-owner state.
- `includes/Core/Plugin.php` plus activation/deactivation/database wiring — shared services, routes, hooks, queue/schema lifecycle.
- existing `QueueManager` / `AsyncJobProcessor` files — only hardening required by canonical queue contract.
- `admin/src/pages/fields-translate.js` — restored/adapted Fields page.
- `admin/src/pages/translations.js` — `fields` tab/route/lazy load only; preserve CPT routing.
- `admin/src/pages/content-translate.js` — Manual placeholders, Auto lock state, Mirror read-only display, explicit force action.
- `admin/src/styles/pages/_translations.scss` and existing imports — required Fields/manual-editor styling only; no inline CSS.
- existing PHP/unit/integration/E2E tests and compiled `admin/dist/` outputs required by normal build/release.
- plugin version/manifest/distribution metadata only through documented distribution builder.

Do not modify unrelated plugins, theme, standalone backend protocol, or shared-checkout transient `tools/tmp/.ctx-mode-*` files.

## Policy Contract

Canonical modes: `auto|manual|mirror`.

Global option: `ipz_acf_default_translation_mode`.

Field property: `ipz_translation_mode`; absence means inherit.

Legacy values retain internal historical behavior until explicit canonical selection:

- `translate` → `legacy_translate`
- `copy` → `legacy_copy`
- `ignore` → `legacy_ignore`

Never silently map legacy `copy` to live Mirror or `ignore` to editable Manual.

Resolution order and structural rules MUST match canonical design. Explicit child beats global fallback. Explicit Manual/Mirror ancestor governs descendants. Repeater/flexible content is one atomic policy owner; group children may differ. Atomic owner identifies policy/revisions/Auto locks/backups. Top-level storage root identifies physical mutation lock.

Persistent runtime keys:

```text
_ipz_acf_auto_locks
_ipz_acf_mirror_backups
```

Exact missing/null/false/empty/zero semantics are mandatory.

## Runtime Service Contracts

`ACFIntegration` exposes purpose-specific behavior equivalent to:

```text
extractAutomatic(int $sourcePostId, int $targetPostId, bool $forceLocked = false): ACFTranslationGraph
extractManual(int $sourcePostId, int $targetPostId = 0): ACFTranslationGraph
extractSourceState(int $sourcePostId): ACFSourceState
snapshotStored(int $targetPostId, array $rootKeys): ACFStoredSnapshot
applyAutomatic(int $targetPostId, ACFTranslationGraph $graph, array $translatedFields): true|WP_Error
applyManual(int $targetPostId, ACFTranslationGraph $graph, array $submittedFields): ACFManualApplyResult|WP_Error
synchronizeMirror(int $sourcePostId, int $targetPostId, ACFSourceState $state): true|WP_Error
restoreStored(int $targetPostId, ACFStoredSnapshot $snapshot): true|WP_Error
```

Exact PHP representation may use repository-style arrays. Every internal caller chooses purpose explicitly. Existing `extract()` may remain compatibility-only.

`ACFMutationLock` MUST acquire every affected `(targetPostId, topLevelStorageRootKey)` in sorted order before snapshot and hold through merge, write, raw verification, compensation, and rollback. Timeout fails closed.

`TranslationJobDispatcher` MUST be the single internal production content-dispatch seam used by REST and source coordinator. Never self-call REST or invoke private controller methods.

Finalizer MUST revalidate, under storage-root locks and immediately before first write:

- source→target→language linkage
- separate core/atomic-owner source revisions
- active policy generation
- target graph revision
- expected Auto lock revisions
- transition write fence
- initiating user’s current `edit_post` authorization for source and target

Mismatch writes nothing. Normal stale automatic work deduplicates one fresh replacement. Stale force work requires new explicit action.

## Matrix Behavior

Implement every row in canonical design:

- New Auto target translates supported text and synchronizes non-text.
- User edit to target Auto owner creates target+owner revisioned lock.
- Normal jobs preserve locked Auto owners.
- Explicit force includes named locked owners and clears only locks whose exact expected revisions were successfully written and verified.
- New Manual target persists no value. API returns source preview separately; UI renders grey non-submitted placeholder.
- Manual target values remain independent. Manual-only source change marks owner/target stale and dispatches no Auto work.
- New Mirror target stores exact source value. Native/plugin target UI is read-only and server rejects target writes.
- Completed source save synchronizes changed Mirror owners 1:1 after ACF writes finish.
- Entering Mirror preserves exact pre-Mirror target state in backup; leaving restores it exactly.
- Changed core dispatches core only. Changed Auto owner dispatches only changed unlocked owner. Mirror-only changes synchronize without translation staleness after success.

Programmatic writes use scoped suppression and never recurse or create Auto locks.

## Source Save and Status Contract

Hook core and ACF save lifecycle so processing runs after final ACF values exist and coalesces duplicate request hooks. Reject autosaves, revisions, target posts, unsupported post types, and reentry.

Store separate deterministic source revisions for core and each atomic owner classified Auto/Manual/Mirror. Track per-target applied revision per owner. Status MUST derive from unresolved revisions/jobs/locks/Mirror failures; successful Auto finalization cannot hide unresolved Manual delta.

Source coordinator uses `TranslationBridge` for linkage and `TranslationJobDispatcher` for work. It never queries translation tables independently from ACF code.

## Two-Phase Policy Transition Contract

Persist permanent global/per-field `policy_generation` plus durable transition rows in plugin-owned DB storage. Initial unseen generation is `0`; generation never resets after success/abort.

PATCH records desired state only. It MUST NOT mutate ACF definition/global option. Affected target owners become read-only behind complete write fence. Normal source coordinator, automatic/force finalizer, manual apply, Mirror sync, and native target edits must defer/reject affected owners.

Queue unit and idempotency key:

```text
(scope, generation, targetPostId, atomicOwnerKey)
```

Each unit snapshots source revisions/generation, acquires top-level storage-root locks before target snapshot, reconciles, verifies, and revalidates before write. Promotion transaction verifies every unit/generation, persists canonical field/global state, then activates desired policy.

Use hardened DB-backed existing `QueueManager` → `AsyncJobProcessor` only after ensuring schema initialization, DB-only durability, atomic claims, bounded batches, three attempts, idempotency, cron activation/deactivation, and authorized routes.

Retry keeps generation and resumes only incomplete units. Abort compensates succeeded units under sorted storage-root locks, verifies restoration, restores previous canonical persistence, and clears fence only after success. Compensation failure leaves failed/read-only state.

## REST Contract

All Fields/reconciliation routes require `manage_options`:

```text
GET   /international-press-zone/v1/translations/fields
PATCH /international-press-zone/v1/translations/fields/default
PATCH /international-press-zone/v1/translations/fields/{field_key}
GET   /international-press-zone/v1/translations/fields/reconciliation/{job_id}
POST  /international-press-zone/v1/translations/fields/reconciliation/{job_id}/retry
POST  /international-press-zone/v1/translations/fields/reconciliation/{job_id}/abort
```

Use exact request/response/status contracts from canonical design, including:

- exact-body rejection
- field grammar `field_[A-Za-z0-9_-]+`
- permanent policy generations in GET and field metadata
- CAS `expected_generation`
- HTTP 409 on generation/concurrent-transition conflict
- active/desired configured/effective state
- durable polling progress/error
- exact empty JSON body for retry/abort
- ACF unavailable GET 200 and mutation 409

Force extends existing `POST /international-press-zone/v1/translate-async`. Accept exact canonical `overwrite_acf` object only with exactly one `target_langs` value matching server-derived `target_id`. Request concurrency values are expectations, never authority. Persist initiator user ID. Require `edit_post` on source/target at dispatch and finalization. Callback HMAC is delivery authentication only.

## Admin Contract

Restore Fields under `#/translations/fields` before Plugins. No top-level sidebar item.

Fields page renders global default, active/desired/legacy/effective policy, nested tree, atomic/ancestor restrictions, search, authoritative serialized mutation, transition polling/retry/abort state, accessible focus/live status, and all loading/error/empty/unavailable/malformed/concurrent states.

Manual editor renders:

- Auto editable values, lock state, and overwrite warning/action
- Manual editable supported text, explicit badge, and source-only placeholder when absent
- Mirror source preview, read-only, absent from submission
- exact permitted token payload

Use real compiled page code in remote fixture. No inline CSS.

## Verification Contract

Extend existing standalone/unit tests and `tests/integration/run-acf-field-transaction-race.sh`. Required coverage is the complete numbered list in canonical design, especially:

- every matrix transition
- target/owner Auto locks and force revision races
- Manual absent storage and stale-only source changes
- material Mirror raw canonical parity and reversible exact backup
- source-save timing/reentry
- top-level storage-root locking before snapshot through rollback
- stale finalizer zero-write behavior
- two-phase fence, promotion, retry, abort, failed compensation
- queue claims/idempotency/attempt/cron lifecycle
- REST capability/CAS/status/exact-body contracts
- legacy behavior until explicit selection
- group sibling preservation and atomic repeater/flexible behavior

Run project-native clean PHP/unit/integration gates discovered from `.claude/agents/expert.md` and package scripts. Resolve every warning/signal.

Admin build:

```bash
cd admin && npm run build
```

Expected: clean success; committed production assets reflect source.

Visual E2E MUST use `~/.claude/bin/e2e-remote` with one repository fixture server + browser workload bound to `127.0.0.1`. Never run local Playwright/dev-server pair; never bypass `tests/e2e/target-safety.js`; never mutate dev1 ACF definitions. Verify canonical visual matrix, accessibility, recovery states, dark/light, and narrow viewport. Store artifacts inside repository tree only as test artifacts; do not commit disposable screenshots unless project convention requires them.

## Release and Deployment Acceptance

- Rebase onto current `origin/master` before final release build.
- All clean release gates pass.
- Run `node tools/build-distribution.mjs`; capture exact generated version/ZIP.
- Run documented archive verification against that exact ZIP.
- Commit source, schema, tests, version, manifest, and built assets as project policy requires.
- If `origin/master` moves, discard stale ZIP, rebase, rebuild (including new patch bump), reverify, rerun affected gates, and commit new release state.
- Fast-forward push `HEAD:master`; never force and never bypass hooks.
- Deploy exact verified ZIP with `node tools/deploy-dev1.mjs <zip>`.
- Run authenticated read-only dev1 smoke: active plugin/version, bundle references, Fields route assets, GET response shape. Deploy tooling verifies `admin/dist/` hashes only; report that scope accurately.

Expected final state: feature is landed on `origin/master`, exact verified ZIP is deployed to dev1, and user can open restored Fields page and observe matrix-compatible policy behavior.

## Out of Scope

- Dynamic Mirror read interception.
- Generic custom-meta policy.
- Mixed descendant policies inside repeater/flexible containers.
- Full complex-field editor in plugin modal.
- Translation-provider/backend protocol changes.
- Top-level sidebar page.
- Changes to abandoned Translate/Multilingual plugin trees or unrelated monorepo projects.
- Deleting/modifying transient shared-checkout diagnostic files.
