# RAY-79436 UAT install runbook — tracking `dev-4.1.x`

Runbook for customer DevOps to install the RAY-79436 fix on a UAT Drupal site by tracking the `drupal/lingotek` `4.1.x` branch via Composer until an official `4.1.3` release is published on drupal.org.

## Scope

- Applies to Composer-managed Drupal 10 sites currently running `drupal/lingotek` **4.1.2**.
- Target: pin `drupal/lingotek` to a specific `dev-4.1.x` commit that contains the RAY-79436 fix (Bug 1 recovery + download concurrency + queue opt-in).
- No database schema changes. `drush updb` is expected to be a no-op.
- Requires: `git`, `composer` (or `ddev composer`), `drush` (or `ddev drush`).

> For DDEV / Lando / Docksal sites, replace `composer` with `ddev composer` and `drush` with `ddev drush` throughout.

---

## Step 0 — Backup

```bash
cd <drupal-project-root>            # the folder that contains composer.json
git status
git checkout -b lingotek-ray79436-uat

cp composer.json composer.json.bak
cp composer.lock composer.lock.bak
tar czf /tmp/lingotek-pre-update-backup.tar.gz web/modules/contrib/lingotek

drush sql-dump --gzip --result-file=/tmp/drupal-pre-update.sql.gz
```

## Step 1 — Confirm current version

```bash
composer show drupal/lingotek | head -5
# or
drush pm:list --type=Module --status=enabled --filter=lingotek --format=table
```

Expected: `4.1.2`. If not, stop and contact Straker.

## Step 2 — Install `dev-4.1.x`

`@dev` relaxes stability for this single package without touching the project-wide `minimum-stability` setting.

```bash
composer require 'drupal/lingotek:dev-4.1.x@dev' -W
```

After this, `composer.json` will include:

```json
"drupal/lingotek": "dev-4.1.x@dev"
```

…and `composer.lock` will lock to the current `4.1.x` branch HEAD commit.

> **Note on the reported version.** Because packages.drupal.org derives the version from `info.yml`, `composer show` will report the installed version as `4.1.3` (the next-version marker on the `4.1.x` branch), **not** `dev-4.1.x`. The authoritative identifier is the commit hash on the `source` line — that is the number to cross-check against the Straker-provided reference. The lock file also carries  
> `extra.drupal.security-coverage.status: not-covered`, which is expected for a branch install — drupal.org security advisories only cover tagged stable releases, so this environment must stay on UAT until the stable `4.1.3` release is published.

## Step 3 — Pin the exact commit (prevent drift)

Two options. **For UAT we now recommend 3B** (commit pin) because the floating `dev-4.1.x@dev` form has caused `composer.lock` / `composer.json` constraint-mismatch failures on customer pipelines (see "Known issue" below). Use 3A only if your release process always installs from `composer.json` (never from `composer.lock`).

### 3A — Track the branch (acceptable but see Known issue)

Keep the `dev-4.1.x` constraint as installed in Step 2. `composer.lock` already pins to a specific commit at install time, so the deployed UAT site will not drift on its own.

Team rule: only update `drupal/lingotek` explicitly, do not run a blind `composer update`:

```bash
composer update drupal/lingotek -W     # only when intentional
composer update                        # must NOT include drupal/lingotek on UAT
```

**Benefit for UAT** (assuming your pipeline is not affected by the `composer.lock` mismatch issue documented below): if Straker publishes a follow-up fix on the `4.1.x` branch, pulling it into UAT is a one-liner — no new patch, tarball, or composer.json edit needed:

```bash
composer update drupal/lingotek -W
drush updb -y && drush cr
```

### 3B — Pin to a specific commit (recommended)

**Recommended for UAT.** This avoids the `composer.lock` / `composer.json` constraint mismatch documented under "Known issue" below, and prevents accidental drift if someone runs `composer update` against the environment.

```bash
REF=$(composer show drupal/lingotek --format=json \
  | php -r 'echo json_decode(stream_get_contents(STDIN), true)["source"]["reference"];')
echo "Locking drupal/lingotek to $REF"
composer require "drupal/lingotek:dev-4.1.x#$REF" -W
```

> If `php` is not on the host (e.g. DDEV-only sites), extract the commit hash manually from the `source` line of `composer show drupal/lingotek` — it is the 40-character string after the repository URL.

`composer.json` becomes:

```json
"drupal/lingotek": "dev-4.1.x#<40-char-sha>"
```

With this pin, `composer update` cannot move `drupal/lingotek` off this commit until the constraint is changed. Trade-off: picking up a later Straker fix requires re-running the `composer require ...#<new-sha>` command.

### Known issue: `composer.lock` mismatch on `dev-4.1.x@dev`

If you used 3A and see this error on a fresh `composer install`:

```
Required package "drupal/lingotek" is in the lock file as "4.1.3" but that does not satisfy your constraint "dev-4.1.x@dev".
```

Cause: packages.drupal.org reports the dev-branch package version as the next-version marker from `info.yml` (here, `4.1.3`). `composer.lock` therefore stores the resolved version as the literal string `"4.1.3"`, but `composer.json` still has the `"dev-4.1.x@dev"` constraint. Composer's strict comparator refuses the match even though both describe the same commit.

Recommended fix: switch to 3B (pin the commit). The constraint `dev-4.1.x#<sha>` resolves to the same canonical form in both files.

```bash
REF=$(composer show drupal/lingotek --format=json \
  | php -r 'echo json_decode(stream_get_contents(STDIN), true)["source"]["reference"];')
composer require "drupal/lingotek:dev-4.1.x#$REF" -W
git commit composer.json composer.lock -m "RAY-79436: pin drupal/lingotek to commit $REF"
```

Workaround (NOT recommended; only as a one-off if you cannot redeploy): manually edit `composer.lock` so the `drupal/lingotek` `version` line reads `"dev-4.1.x"` and matches the `composer.json` constraint. This will be lost on the next `composer update`.

## Step 4 — Commit and deploy

```bash
git add composer.json composer.lock
git commit -m "RAY-79436: track drupal/lingotek dev-4.1.x for UAT fix"
git push origin lingotek-ray79436-uat
```

Deploy the branch through your usual pipeline (Acquia, Pantheon, GitLab CI, rsync, etc). On the UAT server:

```bash
drush updb -y
drush cr
```

## Step 5 — Verify

### 5.1 Installed version + source

```bash
composer show drupal/lingotek | head -10
```

Expected output (shape):

```
name     : drupal/lingotek
versions : * 4.1.3
source   : [git] https://git.drupalcode.org/project/lingotek.git <40-char-sha-from-your-Straker-ticket>
```

- `versions` will read `4.1.3` (branch-head marker from `info.yml`), not `dev-4.1.x` — that is expected when installed via `dev-4.1.x@dev`.
- The **authoritative** check is the 40-character commit hash at the end of the `source` line. It must equal the SHA Straker provided in the ticket you are working from (for example, `dev-4.1.x#<sha>` from Step 3B above). For reference, the v1 RAY-79436 HEAD was `cd64b59e1ccd3808bff5ebdb514e48daf2208575`.
- Drupal's own module page (`/admin/modules`) may show the module version as blank/`VERSION` because `info.yml` has no `version:` key on the dev branch. That is expected and not a problem.

### 5.2 Smoke test

- Visit `/admin/lingotek/manage` and confirm the listing renders.
- Upload one translation-enabled node to Lingotek and download one target language end to end.



## Step 6 — Rollback

```bash
cd <drupal-project-root>
cp composer.json.bak composer.json
cp composer.lock.bak composer.lock
composer install
drush updb -y && drush cr
```

If the module files themselves need restoring:

```bash
rm -rf web/modules/contrib/lingotek
tar xzf /tmp/lingotek-pre-update-backup.tar.gz -C <drupal-project-root>
drush cr
```

DB restore (only if explicitly needed; no schema changes in this update):

```bash
zcat /tmp/drupal-pre-update.sql.gz | drush sql-cli
```

## Behavior change: Download All silently skips non-downloadable targets (v2)

Before v2, clicking **Download All** enqueued every language regardless of its current target status. Languages that were not actually ready to download (PENDING / REQUEST / UNTRACKED / LOCKED / DISABLED / CANCELLED) failed silently inside the queue worker after 3 retries.

In v2, the same languages are filtered out at enqueue time and never enter the queue. The functional outcome is identical (no translation arrives for those languages), but DevOps / content editors may notice:

- The "Queued N translations" status message reports a smaller N than they expected.
- No "maximum retries" errors appear in `drush ws --type=lingotek` for those languages.

If a language is "stuck" in PENDING and the user expects it to come down on Download All, the workflow is to click **Check Translations** (or run `drush lingotek:check-status`) first to refresh the target status from the TMS, then click Download All again. Any language whose status is updated to READY / INTERMEDIATE / CURRENT / EDITED / ERROR will be enqueued on the next Download All click.

## Step 7 — Picking up the next RAY-79436 fix (v2)

RAY-79436 v2 fixes the partition-lock retry storm (Bug B + Bug B-prime), the locale-as-langcode metadata write (Bug D), and the over-eager Download All enqueue (Bug A). To upgrade an environment that is already on the v1 commit (`cd64b59e`):

```bash
# Fetch the new commit hash from the Straker ticket and re-pin (3B):
REF=<new-40-char-sha-from-Straker>
composer require "drupal/lingotek:dev-4.1.x#$REF" -W
drush cr
git commit composer.json composer.lock -m "RAY-79436: pin drupal/lingotek to v2 commit $REF"
```

v2 ships **no schema or post-update hooks** — `drush updb` is not required. A `drush cr` is sufficient to pick up the new code paths. After upgrade, see "Known UX: 'Failed' cron status under contention is benign" below for what to expect from your cron monitoring.

## Step 8 — Upgrading to the future stable `4.1.3`

Once Straker publishes an official release:

```bash
composer require 'drupal/lingotek:^4.1.3' -W
drush updb -y && drush cr
git commit -am "RAY-79436: pin drupal/lingotek to stable 4.1.3"
```

## Known UX: "Failed" cron status under contention is benign

After upgrading to RAY-79436 v2, you may see the `Default cron handler` job (or whichever ultimate_cron job runs the Lingotek download queue) flip to **Failed** in `/admin/config/system/cron/jobs` during periods of heavy concurrent download activity. This is **expected and harmless**.

**What's happening:** when two cron instances try to download the same entity simultaneously, the second worker waits up to 30 seconds for the first to finish. If the wait expires, the worker throws `Drupal\Core\Queue\SuspendQueueException`, which:

- Releases the queue item back to the queue intact (no data loss).
- Stops the queue from processing further items in the current cron run.
- Causes ultimate_cron to log an ERROR and mark this cycle's run as "Failed".

The next cron cycle will pick up where this one stopped and continue draining the queue (typically within 1–2 cycles depending on your cron cadence).

**How to distinguish benign vs real failure:**

```bash
# Benign: SuspendQueueException with the queue-deferral message
drush ws --type=lingotek --severity=warning --count=20 | grep "suspending queue until next cron cycle"

# Real failure: maximum retries exceeded on a single item (this is the v1 pattern; should be 0 after v2)
drush ws --type=lingotek --severity=error --count=20 | grep "maximum retries"
```

If you only see "suspending queue until next cron cycle" warnings and no "maximum retries" errors, the system is operating correctly — items will drain over the next 1–2 cron cycles. If you see "maximum retries" errors after v2, file a follow-up against this ticket.

## Non-Composer sites

If the site stores `web/modules/contrib/lingotek/` in Git (or does not use Composer), this runbook does not apply. Use the tarball / patch artifacts in `RAY-79436-v1-beta1/` instead and follow that README.

## Support

Capture the following and send to the Support team if anything blocks:

- `composer show drupal/lingotek`
- `drush ws --count=50`
- `composer install` or `composer require` full output
- Output of `drush updb -y` and `drush cr`

