# GOLIVE lane R3 proof receipt (PG-30)

Date: 2026-08-22

Status: EXECUTED GREEN on `debian3` (run id `golive-lanes-r3-20260822-proof12`), spec commit
`edd3d8cc5`, base `5a14afde9`. `2 passed (50.7s)`, client exit=0 — chromium 6.5s, firefox 7.6s.

## Command

```
cd <worktree>/plugins/international-press-zone/tests/e2e && \
/home/user/.claude/bin/e2e-remote --hosts debian3 --server ./remote-stack.sh --wait-port 8080 \
  --env WP_BASE_URL=http://127.0.0.1:8080 --env IPZ_E2E_RUN_ID=golive-lanes-r3-20260822-proof11 -- \
  pnpm --dir . exec playwright test --config=playwright.config.js object-authorization.spec.js \
  --project=chromium --project=firefox
```

## Object-level authorization proven at runtime (real browser sessions, cookie + nonce auth)

Fixtures: admin creates two contributor users (owner, intruder), one subscriber, and a draft
post authored by the owner; the owner creates the Hebrew translation through
`PUT /translations/content/{post_id}` (the persisting route). All requests run in-page via
`fetch` with each session's own `X-WP-Nonce`.

| Actor → route | Expected | Proven |
|---|---|---|
| intruder `POST /translation-jobs` (owner's post) | 403 | ✓ |
| intruder `POST /translation-jobs` (intruder's OWN post) | 201 | ✓ |
| intruder `POST /translation-jobs/bulk` (owner's post) | 403 | ✓ |
| owner `POST /translation-jobs` | 201 | ✓ |
| owner `GET /translation-jobs/{jobId}` | 200 | ✓ |
| owner `POST /translation-jobs/bulk` | 201 | ✓ |
| subscriber `GET /translation-jobs/{jobId}` | 403 | ✓ |
| subscriber `POST /translation-jobs/batch-status` | 403 | ✓ |
| intruder `DELETE /translation-jobs/{jobId}` (cancel) | 403 | ✓ |
| intruder `POST /translation-jobs/{jobId}/retry` | 403 | ✓ |
| subscriber `GET /translation-jobs/stats` | 403 | ✓ |
| intruder `POST /workflow/transition` (owner's translation) | 403 | ✓ |
| owner `POST /workflow/transition` | exactly 400 `Failed to log state transition` (see defect below) | ✓ |
| subscriber `GET /workflow/history/{translationId}` | 403 | ✓ |

## Product defect found by this proof (functional, not authorization)

`StateMachine::logTransition()` inserts `from_state`/`to_state`/`user_id` columns that the
`wp_ipz_workflow_states` schema does not define (it has `state`/`previous_state`, `NOT NULL
updated_at`, and `UNIQUE(translation_id)`), and `getCurrentState()` selects the nonexistent
`to_state` — so every workflow transition returns 400 `Failed to log state transition`, for
any authorized user. The feature sits behind the default-off `ipz_team_workflow` flag, which
is why it never surfaced. Tracked as `fire-e73d87ab61765698` on the requests board. The
owner-side assert therefore pins the defect's exact response — status 400 with `Failed to
log state transition` — which only that handler produces past the permission layer; a 401,
403, 404, or 500 fails the spec, and fixing the defect breaks the assert loudly so it gets
upgraded to the real 200 path. The intruder/subscriber 403s are unaffected —
`checkTranslationObjectPermission()` rejects before the handler runs.

## Harness/spec seam fixes this lane needed to reach green (no runtime changes)

1. `13eb38694`/`eddfa5220`/`b77d92757` — legacy jobs table provisioning, response-body
   surfacing, Hebrew language fixture owned by the spec.
2. `0b022d5d1` — create the translation via `PUT /translations/content/{id}`;
   `POST /translations` only reserves a group id and persists nothing.
3. `66ad1fbc6` — provisioning seeds `ipz_feature_flags['ipz_team_workflow']=true` (fail
   closed): `WorkflowController` routes only register behind that flag (`Plugin.php:1261`),
   so a fresh stack 404s every `/workflow` route with `rest_no_route`.
4. `8883057ae` — provisioning runs the idempotent `Database::create_tables()` (fail closed on
   `ipz_workflow_states`): the snapshot ships with the plugin already active, so the
   activation hook that creates tables never fires.
5. `77782c9d5` — wp-login navigation intermittently stalls on the remote stacks
   (cross-browser, cross-host); each login attempt now gets a fresh browser context with a
   short per-attempt timeout, up to 3 attempts.
6. `6a1770f76` — dropped the end-of-test Hebrew cleanup: chromium's disable fallback made
   firefox's fixture creation 409 (`language_disabled`); the stack is disposable per
   dispatch, so nothing outlives the run.

## Run history to green

- proof4 (debian1): chromium fail at translation read 404 (non-persisting POST /translations);
  firefox login hang.
- proof5 (debian1): translation creation fixed; both browsers hit `/workflow/transition` 404
  — diagnosed as the `ipz_team_workflow` route gate, not authorization.
- proof6 (debian3): workflow routes live, intruder 403 correct; owner transition 400 — the
  `ipz_workflow_states` table was missing (activation hook never re-fires).
- proof8/9 (debian3): table created; owner transition still 400 — diagnosed as the
  `logTransition` schema mismatch (real product defect, filed).
- proof10 (debian3): chromium GREEN end-to-end; firefox 409 on the Hebrew fixture after
  chromium's cleanup disabled the language.
- proof11 (debian3): both browsers GREEN, exit 0 (pre-review asserts).
- proof12 (debian3): both browsers GREEN, exit 0, with the review-hardened asserts
  (`edd3d8cc5`): defect-pinned owner 400, intruder own-post positive control, strict
  Hebrew-row fixture.

## Independent review (gpt-5.6-sol, medium effort) — disposition

Verdict as returned: BLOCKED. Parent disposition: all findings accepted and fixed in
`edd3d8cc5`; proof re-run green as proof12. No runtime code involved.

- **High — `not 401/403` owner assert accepts 404/405/422/500: ACCEPTED, fixed.** The spec
  now pins exactly status 400 with the defect's `Failed to log state transition` message —
  the one response only the post-authorization handler produces — and will fail loudly when
  `fire-e73d87ab61765698` is fixed, forcing the upgrade to the 200 path.
- **Medium — intruder had no authorized positive control: ACCEPTED, fixed.** The intruder now
  owns a post and successfully queues a job on it (201), distinguishing object-level denial
  from actor-wide denial (dead session/nonce or blanket capability failure).
- **Medium — workflow fixture silently fell back to an arbitrary translation: ACCEPTED,
  fixed.** The Hebrew row is now required (`expect(hebrewRow).toBeTruthy()`); no fallback.
- **Medium — login retry leaked contexts (`newPage` outside try; nonce fetch after the
  loop): ACCEPTED, fixed.** `newPage` moved inside the try; a nonce-fetch failure closes the
  surviving context before rethrowing.
- **Low — flag seed replaced the whole `ipz_feature_flags` option: ACCEPTED, fixed.**
  Provisioning now merges the flag into the existing map (fresh stacks have no option, so
  behavior is identical there; the read-back check still fails closed).
