# D7_FREEZE_CHECKLIST.md

**Decision D3-3 — the formal D7 freeze point, required before Phase 3.5.**
Prepared 2026-08-25. **Not scheduled. Not executed.**

> ⚠️ **The implementation must never freeze, stop, disable or modify D7.**
> The freeze is a business/operational action. This document is the procedure to
> follow *when you decide to run it* — nothing here is self-executing.

---

## 1. Why a freeze is required

`RECONCILIATION.md` asserts exact counts. An exact count is only true at an instant. Evidence
gathered 2026-08-25 (SELECT-only):

| Table | Newest write |
|---|---|
| users · nodes · flaggings | 2026-08-11 |
| quiz results | 2026-08-10 |
| **orders + order revisions** | **2026-08-24 13:31:48** |

D7 is **effectively static but not formally frozen**. Business activity ceased 2026-08-11; a single
order then appeared on 2026-08-24 — two weeks later, and 3h 42m *before* the audit checkpoint commit
`70fee084e`. One write after weeks of silence is exactly the pattern a freeze exists to rule out.

Acceptance criteria expressed **relative to the freeze point** rather than as literals, which cannot
be evaluated without one:

- **R-21** — archived `commerce_order_revision` rows = source population at the freeze point (D3-2)
- **R-5** — `32,745 live + 258 logged = 33,003`, re-measured at the freeze point (D3-4)
- **R-24** — 1,213 total orphans, re-measured at the freeze point (D3-5)
- **R-18** — one inactive `commerce_discount` rule still unaccounted for (94 vs 93)
- **U-16 residual** — the **user population** behind the 105 update/delete ACL bindings has never
  been measured. D3-7 requires it before implementation (D3-7)

---

## 2. Pre-freeze — must be true before the freeze is declared

| # | Condition | Status |
|---|---|---|
| P-1 | **Task 11 backups exist and are verified** — current database, current files, DDEV snapshot, all outside both repos | ✅ **MET 2026-08-25** — `d7-20260825-123510`, 8/8 checks passed |
| P-2 | Backups confirmed to contain *current* data, not the stale 2025-07-04 snapshot | ✅ **MET** — `commerce_order` 87,640 in the dump, corroborated by users 13,250 · acl_user 33,003 · flagging 132,256 · line items 116,472 |
| P-3 | U-10 decided — how R-5 is expressed given 258 unrecreatable grants | ✅ **MET 2026-08-25** — decision **D3-4**, composite assertion approved |
| P-4 | U-14 decided — R-23 443 vs 1,213 measured | ✅ **MET 2026-08-25** — decision **D3-5**, R-23 stays 443, new R-24 = 1,213 |
| P-5 | U-15 decided — Wistia destination column type | ✅ **MET 2026-08-25** — decision **D3-6**, VARCHAR, no FK |
| P-6 | U-16 decided — update/delete ACL grants (105 ACLs) | ✅ **MET 2026-08-25** — decision **D3-7**, all three grant flags preserved |
| P-7 | Business owner has scheduled the freeze window and notified whoever administers D7 | ✅ **MET 2026-08-25** — scheduled for **2026-08-27 21:00 IST**, see §2a |
| P-8 | D7 read-only connection still provably SELECT-only | ✅ proven Task 06 |
| P-9 | Reconciliation suite runs clean | ✅ 6 scripts, R-1…R-23 + L-1…L-10 |

**ALL NINE PRECONDITIONS ARE MET.** The freeze is **scheduled but has not yet occurred** — see §2a.

---

## 2a. ✅ P-7 — freeze window scheduled

Supplied by the business owner **2026-08-25**:

| Field | Value |
|---|---|
| **Freeze date** | **2026-08-27** |
| **Freeze time** | **21:00 IST** (UTC+05:30) |
| **Freeze instant, UTC** | **2026-08-27 15:30:00 UTC** |
| **Freeze instant, Unix epoch** | **`1787844600`** |
| **Mechanism** | Block D7 admin logins + notify all D7 administrators |
| **Notified** | D7 administrators / operations team |

### ⏳ The freeze is in the FUTURE — 2 days and 21 hours from now

Today is **2026-08-25**. Nothing in §4 may be run before `1787844600`. Every figure currently in
`DECISIONS_PHASE_3.md` — including the approved R-5 `32,745 + 258`, R-24 `1,213` and the D3-1
Rules split — **remains provisional until re-measured at that instant**.

### 🛑 The mechanism needs one clarification before 2026-08-27

*"Block D7 admin logins"* can be implemented three ways, and **they are not equivalent for this
migration**:

| Implementation | Writes to D7? | Verdict |
|---|---|---|
| **Web-server / firewall / network block** of `/user/login` and `/admin` | ❌ none | ✅ **PREFERRED — zero D7 writes** |
| **Drupal maintenance mode** (`variable_set('maintenance_mode', 1)`) | ⚠️ one row in `variable` | ⚠️ **Acceptable** — non-business config. Must be applied **before** the §4 capture, never during |
| **Blocking the admin USER ACCOUNTS** (`users.status = 0`) | 🛑 **business data** | ❌ **MUST NOT BE USED** |

⚠️ **Why the third option would break the migration.** Setting `users.status = 0` writes to the
`users` table — the same column the reconciliation reads. It would change the live blocked-user
count (currently exactly **27**, which is also the size of a preserved exception set), corrupt the
R-1 baseline of 13,249, and alter historical business data that decision 14 requires preserved
**exactly**. It would also make the §4 "did the freeze hold?" check fail by design, because the
newest `users` write would land *after* the declared instant.

**Please confirm the operations team is using the network-level block or maintenance mode — not
account blocking.** If accounts have already been blocked, that is a STOP condition: the change must
be reported and the baseline reconsidered, **not** silently absorbed.

ℹ️ Blocking *admin* logins does not by itself stop **customer** checkout, which is what produced the
lone 2026-08-24 order. Business activity has been silent since 2026-08-11, so the residual risk is
low — but §4's verification is what actually proves the freeze held, and it covers orders too.

---

## 3. The freeze itself — YOUR action, not the implementation's

Whatever mechanism you choose (maintenance mode, blocking admin logins, an
operational instruction to staff), the implementation performs **none of it**.

**Scheduled 2026-08-25. Not yet executed.** Record the actual outcome here on the day:

```
FREEZE SCHEDULED: 2026-08-27 21:00:00 IST (UTC+05:30) = epoch 1787844600
MECHANISM:        Block D7 admin logins + notify all D7 administrators
NOTIFIED:         D7 administrators / operations team

--- fill in ON THE DAY ---
ACTUALLY APPLIED AT:  ____-__-__ __:__:__ IST
APPLIED BY:           ______________________
IMPLEMENTATION USED:  [ ] network/webserver block   [ ] maintenance mode   [ ] OTHER -> STOP
CONFIRMED no users.status was changed:  [ ] yes
```

**After this instant, no D7 business or administrative write may occur.**

---

## 4. At / after the freeze point — verify, THEN capture the baseline

> ⏰ **Not before `2026-08-27 21:00:00 IST` (epoch `1787844600`).**
> `freeze_verify.sh` refuses to run early — it exits with status **2** and captures nothing,
> so a premature run cannot contaminate the baseline.

### ⭐ One command does all of it

```bash
bash /Users/apple/neerja/ceonline-d10/scripts/freeze/freeze_run.sh
```

Run **on the host**, not inside ddev. It performs steps 1–3 below in order and **stops at the first
failure**, so proceeding is never a judgement call about printed text:

| Exit | Meaning |
|---:|---|
| **2** | Freeze instant not reached. Nothing run, nothing captured |
| **1** | A gate failed. **No baseline captured** — only the verify log is kept as evidence |
| **0** | Verified, captured, copied to `/Users/apple/neerja-backups/freeze-<stamp>/` with `SHA256SUMS.txt` + `MANIFEST.txt` |

All three paths were exercised against a stubbed harness before hand-off: early-run refuses; gate
failure produces **no** baseline logs; success produces all five plus checksums.

It also re-verifies **D7 is unchanged** (`course_actions.module` md5 + 11 git entries) and **fails
the run** if either differs — that check gates the result rather than merely printing.

The steps below are the manual equivalent, for reference or partial re-runs.

### Step 1 — verify the freeze held, and close the U-16 residual

```bash
cd /Users/apple/neerja/ceonline-d10
ddev exec bash /var/www/html/scripts/reconcile/freeze_verify.sh > .logs/FREEZE-verify.log 2>&1
echo "exit=$?"   # 0 = proceed · 1 = STOP · 2 = too early
```

`freeze_verify.sh` ends with a **machine-readable gate** that recomputes F-1…F-4 independently of the
human-readable output and **exits non-zero** on any failure or on R-5 drift.

Four checks, all read-only:

| | Check | Pass condition |
|---|---|---|
| **F-1** | Did the freeze hold? | Every row reads `ok`. **Any `VIOLATION` → STOP** |
| **F-2** | `users.status` integrity | **blocked must still be exactly 27** — proves account blocking was *not* used |
| **F-3** | U-16 residual (D3-7) | Measures the **user population** behind update/delete grants — never measured before |
| **F-4** | R-5 composite (D3-4) | `32,745 live + 258 orphan = 33,003` |

**Why F-1 compares raw Unix integers, not `FROM_UNIXTIME()` strings.** `FROM_UNIXTIME()` renders in
the MySQL *session* time zone. The D7 container, the host and the business need not agree, and a
silent 5h30m offset would make a post-freeze write look compliant. Drupal 7 stores these columns as
raw integers, so the comparison is integer-to-integer and cannot be shifted. The readable column is
printed for humans only and is never used for the verdict.

⚠️ **`users.access` / `users.login` move on any login**, including a customer merely browsing. They
are reported for visibility; a violation there is less serious than one in orders, nodes or
flaggings — but it still proves the site was reachable and must be **reported, not ignored**.

### Step 2 — capture the acceptance baseline

**Only after F-1 shows no violation and F-2 shows blocked = 27:**

```bash
ddev exec bash /var/www/html/scripts/reconcile/d7_baseline.sh         > .logs/FREEZE-baseline.log      2>&1
ddev exec bash /var/www/html/scripts/reconcile/d7_relationships.sh    > .logs/FREEZE-relationships.log 2>&1
ddev exec bash /var/www/html/scripts/reconcile/d7_orphans.sh          > .logs/FREEZE-orphans.log       2>&1
ddev exec bash /var/www/html/scripts/reconcile/d7_roles_permissions.sh > .logs/FREEZE-roles.log        2>&1
```

**That run — and only that run — is the migration acceptance baseline.** Every earlier measurement
in this project is provisional, **including every figure quoted in `DECISIONS_PHASE_3.md`**: R-5's
`32,745 + 258`, R-24's `1,213`, the D3-1 split of `121 + 16 + 9 = 146`, and the 194 / 27 / 64
exception sets.

⚠️ `.logs/` is **gitignored**. These four logs plus `FREEZE-verify.log` are the evidence the whole
acceptance argument rests on — **copy them somewhere durable**, alongside the Task 11 backups at
`/Users/apple/neerja-backups/`, outside both repositories.

### Step 3 — if anything differs from the provisional figures

**Report it. Do not repair it, and do not quietly update a target to match.**
`CLAUDE.md` §25: *if they differ, STOP.* A difference at the freeze point is information about the
source, not an error to be tidied away.

---

## 5. During the freeze

| Rule | |
|---|---|
| D7 stays **running and reachable** | it is the golden master for comparison (`CLAUDE.md` §32) |
| D7 stays **read-only** to the migration | `d10_migration_reader`, SELECT only |
| **No** D7 code, config, schema or data change | including "obvious" fixes |
| Re-run reconciliation freely | it is read-only and cannot disturb the freeze |
| If a business write becomes unavoidable | **stop the migration**, record it, re-baseline from a new freeze point |

---

## 6. Freeze release

Only after the D10 acceptance gate passes:

- reconciliation matches the freeze-point baseline
- the T0 end-to-end workflow passes
- **no user gains or loses access relative to D7** (R-14 / R-15)

Releasing earlier means the baseline no longer describes the source, and the migration can no longer
be proven correct.

---

## 7. What is prepared and waiting

| Artifact | State |
|---|---|
| `scripts/backup/d7_backup.sh` | ✅ **executed 2026-08-25**, artifacts verified |
| `scripts/backup/verify_backup.sh` | ✅ independent 8-check verifier, re-runnable at any time |
| `scripts/reconcile/*.sh` (6) | ready, read-only, exercised against live D7 |
| `scripts/reconcile/freeze_verify.sh` | ✅ **new 2026-08-25** — F-1…F-4 + machine-readable gate; **refuses to run before epoch 1787844600** |
| `scripts/freeze/freeze_run.sh` | ✅ **new 2026-08-25** — host runbook: verify → hard gate → baseline → durable copy. All three exit paths tested |
| `web/modules/custom/ce_migrate/ce_migrate.install` | orphan log + reconciliation-run schema, **not installed** |
| This checklist | ready |

**Nothing here executes on its own.**
