M MWM Platform

Changelog

# MWM (Maintenance Windows Management) — Changelog

Newest first. The version is shown as the badge in the UI (click it → this page).

## 2.76 — 2026-07-08
- **Stakeholder admins can manage their own people**: users holding
  `manage_stakeholder` now see the Users page filtered to their stakeholder and can
  create, edit, welcome, deactivate and delete those contacts. New users they create are
  forced into their stakeholder; they cannot touch other orgs' users, platform admins,
  or grant/remove admin roles.
- **Users list redesign**: the Name column now leads the table (bigger, bold), email
  second. The password field on the edit form now says explicitly that typing there
  OVERWRITES the current password.
- **Impact is tenant-team only**: the Impact nav item and its ABM are admin-only, and
  stakeholder contacts no longer see or can change the Impact field when creating or
  editing an MDT (existing classification is preserved).

## 2.75 — 2026-07-08
- **Self-service password reset**: "Forgot your password?" on the login page mails a
  signed set-password link (7-day expiry, same mechanism as onboarding), branded and in
  the user's language. The response never reveals whether an address exists; requests
  are logged with IP.
- nginx now serves the ACME challenge webroot (groundwork for the Let's Encrypt
  certificate).

## 2.74 — 2026-07-08
- **Reschedule asks for the new window right there**: the Reschedule button on the MDT
  detail now carries date + start/end time fields; the change is validated (no past
  dates, end after start) and the history records "rescheduled: old → new".
- **Today's calendar cell now has a black frame** (was brand-green, easy to miss).

## 2.73 — 2026-07-08
- **Drag & drop on the calendar**: drag an MDT box to another day to move its window.
  Approved (Scheduled) MDTs warn first and go back for re-approval — same rule as
  editing — and the move is recorded in the history ("window moved A → B, calendar
  drag"). Past dates and closed/cancelled MDTs are rejected; the target day highlights
  while dragging. Available to users who can request/approve/edit MDTs.

## 2.72 — 2026-07-08
- **Automatic translation of MDT descriptions in emails** (optional, per tenant): paste a
  Google Cloud Translation API key in Settings and the free-text description goes out in
  each recipient's language (EN/ES/AR), with a "Test translation" button. Results are
  cached; any API failure falls back to the original text — notifications never block.
  No key = descriptions as written (default).

## 2.71 — 2026-07-08
- **MDT emails now include the Description** you entered, in the HTML details card and
  the plain-text body. The label comes out in each recipient's language (the free text
  itself is sent as written — automatic translation of it is in the backlog: it needs an
  external translation service).

## 2.70 — 2026-07-08
- **Wizard fixed**: Back / Next / Save showed all at once on every step (a CSS display
  rule was overriding the `hidden` attribute), so Next was still visible on the Review
  step and clicking it did nothing. Now each step shows exactly its own buttons — Review
  shows only Save.
- **"Working…" feedback on every form**: submit buttons show a spinner and stop taking
  clicks while the request is in flight, so a slow save no longer looks dead.

## 2.69 — 2026-07-08
- **Executor shown by full name**: the executor selector (wizard and classic form) now
  reads "Name — email" instead of the bare address, and the MDT detail shows the
  executor's and requester's name with the email as secondary text.

## 2.68 — 2026-07-08
- **Stakeholders page is tenant-admin only**: the nav item is hidden and all its routes
  (list, create, edit, delete) now require the admin permission.
- **Stats are scoped by visibility**: a stakeholder contact now sees KPIs computed ONLY
  over the MDTs they can fully see (an INDRA user gets INDRA numbers). Admins, approvers
  and internal users keep tenant-wide stats; scheduled KPI reports stay tenant-wide.

## 2.67 — 2026-07-08
- **Edit notify-stakeholders from the MDT detail** (admins/approvers): a new "Edit
  stakeholders to notify" box changes ONLY who gets notified / who sees the MDT — no
  status reset, no re-approval (the full Edit still voids the approval as before). The
  add/remove diff is recorded in the history. The detail page now also shows the
  notify list for everyone.

## 2.66 — 2026-07-08
- **Stakeholder visibility is now enforced everywhere and configurable per tenant**
  (Settings → Notifications → Stakeholder visibility). A stakeholder contact used to see
  every MDT on the dashboard calendar. Three modes:
  - **Only their own MDTs** (new default) — the calendar, MDT list, dashboard queues and
    Outlook feed only show MDTs touching their organization (their services, explicit
    notification, or being executor/requester).
  - **Number only** — they see every box on the calendar but foreign MDTs carry just the
    MDT number and window: no title, services or people; opening the detail is blocked.
  - **Everything** — previous behavior.
  Internal users, admins, approvers and stakeholders flagged "sees all" always see
  everything in full.

## 2.65 — 2026-07-08
- **Activity log for tenant admins** (nav → Log): everything that happened in the
  workspace across all MDTs, newest first — lifecycle transitions, every notification
  that went out (with recipients and channel: "mail to / telegram to / cc"), and executor
  answers. Filterable by type and by MDT, paginated, each row links to its MDT.

## 2.64 — 2026-07-08
- **Designed status + actions on list views**: user status is now a badge with a colored
  dot (green Active / gray Inactive) and the row actions (edit, welcome, deactivate,
  delete) are chip buttons with hover states — applied consistently to every list view
  (Users, Services, Stakeholders, Impact, platform Tenants).

## 2.63 — 2026-07-08
- **Design pass on list views**: the page container widened (960 → 1240 px) so the Users
  table no longer clips the action buttons on normal screens.
- **Phone field on users**: new contact phone in the user form, shown under the name in
  the list.
- **Wider emails**: the branded email shell grew from 560 to 720 px — long lines no longer
  wrap unnecessarily.
- The welcome email and user form now say **"Telegram bot"** instead of "tenant bot" (all
  three languages).
- **Close (admin)**: tenant admins can force-close an MDT from any open status (next to
  DELETE, with outcome select); recorded in the audit trail as "closed by admin (forced)".

## 2.62 — 2026-07-08
- **Security package**:
  - Login brute-force lockout: 5 failed attempts lock the account for 15 minutes
    (works across workers; failures and lockouts logged with IP).
  - HTTP hardening headers on every response: CSP, X-Frame-Options DENY, nosniff,
    Referrer-Policy, Permissions-Policy, HSTS.
  - Cross-site POST rejection (Origin/Referer check) on top of SameSite cookies.
  - Minimum password length raised to 8 (set-password links, user create/edit).
- **Per-tenant access control (geo-filtering)**: Settings → Access control — allowed
  countries (ISO codes) and/or always-allowed IP ranges (CIDR). Empty = open. Country
  lookup via local DB-IP database (downloaded at deploy; fails open if absent). The
  settings page shows your current IP/country so you don't lock yourself out; escape
  hatch: `flask access-reset <slug>`. The platform subdomain is never filtered.

## 2.61 — 2026-07-08
- **Fixed clipped action buttons** on list views (Users, Services, Stakeholders, Impact,
  Tenants): the actions column no longer overflows the table — rows keep their height and
  the table scrolls horizontally when narrow. Also removed a glyph that rendered as a
  broken box on some Windows browsers.

## 2.60 — 2026-07-07
- **Tenant logo**: new Branding box in Settings with a logo URL. The logo shows in the
  top bar, in every branded email and in scheduled reports (falls back to the MWM mark).
- **Scheduled reports**: new box in Settings — pick report type (Upcoming windows /
  Activity summary / KPI summary), frequency (daily / weekly / monthly + day), time
  (tenant timezone) and recipient emails. Reports go out automatically via the scheduler,
  localized per recipient; each row has Send now / Pause / delete.

## 2.59 — 2026-07-07
- **DELETE button for tenant admins**: admins can now permanently delete any MDT from its
  detail page, whatever its status (confirm dialog warns that history goes with it).
  Non-admins still can't delete anything.

## 2.58 — 2026-07-07
- **Quick user deactivation**: an activate/deactivate action on every user row. Inactive
  users can't log in, receive no notifications, and executor prompts are skipped for them
  (recorded in the MDT timeline).
- **Always CC redesigned**: its own box — pick a user, hit Add; each CC person shows with a
  ✕ to remove. No more multi-select gymnastics. Saving the settings form no longer touches
  the CC list.
- The cancel transition button now reads **"Cancel MDT"** (it looked like a page-exit) —
  action buttons are also translated now.

## 2.57 — 2026-07-07
- **The 44 historical maintenance windows from v1 are now in MWM** (imported with their
  original refs, statuses mapped — Pending Review→Submitted, Approved→Scheduled,
  Completed→Closed✓, Failed→Closed✗ — services, stakeholders, executors and creation dates
  preserved; four refs that collided with today's tests carry a “-v1” suffix). The scheduler
  is suppressed for past-window imports, and Stats/calendar now show the real history.

## 2.56 — 2026-07-07
- **Editing an approved MDT voids the approval**: it goes back to Submitted (audited, with a
  clear flash) and the executor circuit is cleanly re-armed — on re-approval the questions
  fire again. Previous-attempt checkpoints stay in History marked 🗄.
- **Rescheduled is now a real state**: an approver can send a Scheduled window to
  Rescheduled; it stays editable and open (visible in dashboard queues), then re-Submit →
  re-approve restarts the full circuit. Date history preserved.

## 2.55 — 2026-07-07
- Stats: **monthly series** (last 12 months) — windows, emergencies, hours, closed OK/failed
  per month.

## 2.54 — 2026-07-07
- **Overlap warning**: creating or editing an MDT that shares services with another open MDT
  on an intersecting window shows a heads-up with the conflicting refs (non-blocking).
- **Advance notice finally fires**: subscribers of approved-events get a localized
  "maintenance window coming up" reminder `advance_notice_hours` before the window
  (once, tracked in the timeline).
- **Silent-executor reminder**: if the executor doesn't answer the pre-check within 30
  minutes, the question is re-sent once (tracked as "Reminder sent to executor").

## 2.53 — 2026-07-07
- **Automatic daily Postgres backups**: new `backup` container dumps the database (gzip) to
  `/home/somtik/mwm/backups/` every 24h — first dump right at startup — keeping 14 days of
  rotation. Restore: `gunzip -c backups/mwm-DATE.sql.gz | docker compose exec -T db psql -U
  postgres mwm`.

## 2.52 — 2026-07-07
- **Classic | Wizard toggle** on New MDT — two buttons like the language switcher; the choice
  is remembered.
- **Per-tenant weekend days on the calendar**: Settings → Notifications lets each tenant pick
  its weekend (RCRC default: Friday+Saturday); those days get a gray-blue tint so at a glance
  you avoid scheduling when few people are around. Nothing hardcoded.
- **Welcome email, resendable per user, in THEIR language** ✉: new "welcome" button on each
  user row sends a branded mail (workspace URL, their login, Telegram activation code) with a
  **Set your password** button for no-password contacts — a signed 7-day link to a public
  set-password page (v1-style onboarding, trilingual). Users who already have a password get
  an "Open MWM" button instead.

## 2.51 — 2026-07-07
- **New MDT wizard** ✨: when creating an MDT you can switch between the classic single form
  and a **step-by-step wizard** with a progress bar (Basics → Window → Services → Impact &
  notify → Executor → Review). The review step summarizes everything before saving; your
  choice of format is remembered. Same fields, same validations, same endpoint.
- Fix: recovered 296 ES + 305 AR translations that a broken catalog extraction had silently
  emptied (v2.50 shipped with gutted catalogs for a few minutes). The catalog pipeline now
  syntax-checks the tree before extracting and validates completeness after.

## 2.50 — 2026-07-07
- **Subscribe to MWM from Outlook/Google/Apple Calendar** 📅: new per-user iCalendar feed —
  the "Subscribe" button on the dashboard shows your personal secret URL; paste it once in
  your calendar app and every MDT appears as an event (window times in the tenant timezone,
  ref + status + ⚡ in the title, services/executor/link in the description, cancelled events
  marked). It refreshes on its own — live as the calendar fills up. Honors stakeholder
  visibility: each person's Outlook shows what they'd see in the web.

## 2.49 — 2026-07-07
- **Every notification is now an INDIVIDUAL message in the recipient's language.** Event
  mails go one-per-recipient rendered in each user's locale (EN/ES/AR) — subject, body and
  the branded details card. The executor questions (ready / start / result) and their buttons
  arrive in the executor's language, by mail and Telegram. Bot replies (/activate) localized
  too. The Always CC people get exactly ONE archive copy per event, in the tenant language.

## 2.48 — 2026-07-07
- **New Stats page** (📊 button in the nav bar): live KPI cards — total windows, executing
  right now, total/avg hours in maintenance, emergencies, started on time vs late (10-min
  grace), never-confirmed windows, closed OK/failed — plus breakdowns by status, by executor
  (windows + hours), by service and by stakeholder.
- **Execution queue on the dashboard**: each executor with their open-window count and
  clickable refs — spot the overloaded one before assigning more (count turns gray at 5+).

## 2.47 — 2026-07-07
- Dashboard queues panels now always visible (with a friendly "nothing waiting" note when
  empty) instead of hiding when there is no pending work.

## 2.46 — 2026-07-07
- **Dashboard queues**: two new panels under the calendar — **Approval queue** (each approver
  with their count and the clickable MDT refs waiting on them; ⚡ marks emergencies, which
  only appear in the queues of Emergency Approvers/admins) and **By stakeholder** (each
  stakeholder with its open MDTs). Sorted by load, live as MDTs move.

## 2.45 — 2026-07-07
- Backlog: **iCalendar (ICS) subscription feed** — per-user secret URL so Outlook/Google/Apple
  calendars subscribe once and get every MDT live (window times in tenant timezone, ref +
  status, link to detail), honoring stakeholder visibility; optional holidays/PTO overlay and
  a "Subscribe 📅" button on the dashboard.

## 2.44 — 2026-07-07
- Backlog: **user PTOs** (self-service vacations visible as a calendar overlay with toggle,
  executor-availability warnings) and **custom per-tenant holidays ABM** (incl. change
  freezes, like v1's change_freeze).

## 2.43 — 2026-07-07
- **Saudi holidays on the calendar** with an instant on/off toggle button right on the
  calendar header (🕌 Holidays: ON/OFF, remembered per session). Islamic dates (Eid al-Fitr
  1–3 Shawwal, Arafat Day, Eid al-Adha 10–12 Dhu al-Hijjah) computed from the official
  Umm al-Qura calendar (hijridate); civil dates (Founding Day Feb 22, Saudi National Day
  Sep 23) included. Holiday cells get an amber tint and the holiday name, localized in the
  3 languages.

## 2.42 — 2026-07-07
- Settings → Mail: new **Always CC** multi-select of users — every outgoing mail (event
  notifications, executor questions, emergency alerts) CCs the selected people. De-duplicated
  against the To list; supports multiple selections.

## 2.41 — 2026-07-07
- Emergency timing refined per spec: **READY question immediately on approval; START question
  waits for the window time; RESULT question fires the moment start is confirmed.** (2.40 had
  the start question firing right after ready — corrected.)

## 2.40 — 2026-07-07
- **Emergency circuit — everything immediate.** An MDT whose window falls inside the tenant's
  emergency threshold is auto-classified as EMERGENCY (⚡) on create/edit. At creation, the
  Emergency Approvers (or admins as fallback) get an instant "awaiting approval NOW" alert
  with a Review & approve button. Only an Emergency Approver (or admin) can approve it — and
  on approval the executor gets the "all good?" question IMMEDIATELY (no pre-check wait);
  when they confirm, the "do we start?" arrives right away (no window-start wait). No queues,
  no timers: create → alert → approve → confirm → run.

## 2.39 — 2026-07-07
- Backlog: the whole Telegram bot (menus, buttons, executor prompts, /activate replies)
  localized in each user's chosen language (EN/ES/AR), same as the per-recipient emails.

## 2.38 — 2026-07-07
- Backlog: native in-chat Telegram confirmations for the executor (callback buttons instead
  of opening the browser). Note: mail + Telegram start-confirmation already works since 2.23.

## 2.37 — 2026-07-07
- Backlog: added **interactive Telegram bot menus per user, driven by their access levels**
  (Requesters draft, Approvers approve from the chat, Executors confirm their windows,
  Notifications-only users browse the calendar — same RBAC as the web).

## 2.36 — 2026-07-07
- New **BACKLOG.md** at the project root: the living improvements list. First user-requested
  item: **per-recipient individual emails in each user's preferred language** (instead of one
  bulk email with everyone in To) — also for Telegram. Plus the accumulated pending items:
  emergency circuit, KPIs, editable catalogs (ABMs), zero-downtime deploys, v1 cutover,
  Postgres backups.

## 2.35 — 2026-07-07
- **Click a calendar day to create an MDT on that date**: the day number is now a link that
  opens the New MDT form with the window date pre-filled; hovering a day shows a green “+”
  and highlights the cell.

## 2.34 — 2026-07-07
- **Visual lifecycle timeline on the MDT detail** (top of the page): a red progress line with
  a dot per stage — Drafted → Submitted → Scheduled → Executed → Closed. Reached stages are
  filled, the NEXT step pulses with a "next step" tag, and under each dot you see what
  happened there (approvals, mails/Telegram sent, executor answers) with times. Cancelled
  shows as a dark terminal dot with the remaining stages dimmed. RTL-safe.

## 2.33 — 2026-07-07
- **History now lists exactly who was notified and how**: each "Notification sent" entry
  details `mail to: …` and `telegram to: …` with the actual addresses. Executor prompts also
  record their channel and address.
- **New MDT form: "Stakeholders to notify"** — pick which stakeholders get alerted for this
  MDT (their contacts with notification roles), in addition to the owners of the affected
  services (v1's mdt_stakeholder brought back).
- **Branded HTML emails** — the v1 email look, in MWM green: brand header, details card
  (Ref/Title/Window/Services/Status), and big action buttons for the executor questions.
  Plain-text fallback included.
- MDT numbering now starts at **MDT-00100** so it never collides with the v1 numbers.

## 2.32 — 2026-07-07
- Fix: 27 Spanish/Arabic translations were silently wrong — `pybabel update` fuzzy-matched new
  strings to old similar entries (e.g. the Arabic dashboard title showed "Maintenance windows."
  instead of "Maintenance Calendar"). All recent strings force-corrected; the catalog process
  now overwrites instead of only filling blanks.

## 2.31 — 2026-07-07
- **The dashboard is now the Maintenance Calendar** (like v1): month grid with each MDT as a
  colored pill (status colors, start time, ⚡ for emergencies), Prev/Today/Next navigation,
  today highlighted, click-through to the MDT. Below it, the **Open MDTs** table (newest
  first). The module cards are gone — the nav bar already covers them.
- Nav bar and language switcher got hover feedback; calendar localized in the 3 languages
  (month/day names via locale).

## 2.30 — 2026-07-07
- **Unified History on the MDT detail**: one chronological timeline with EVERYTHING —
  lifecycle transitions, every question sent to the executor, every executor answer, and
  every notification batch (with `event: reached/recipients` detail). The separate
  "Executor flow" table is gone.
- MDT list: removed the redundant "open" link (the Ref is the link).

## 2.29 — 2026-07-07
- Fix: 500 on POSTs that commit and then load data (e.g. Cancel/transition an MDT, sometimes
  creating one). The RLS context hook read `g.tenant.id` from an ORM object expired by the
  commit, re-entering connection acquisition. The hook now uses a plain-value snapshot taken
  at request start. (Postgres-only bug, invisible to the sqlite test suite — page sweeps now
  include real POST cycles.)

## 2.28 — 2026-07-07
- **Per-user notification channels**: each user chooses **Email + Telegram / Email only /
  Telegram only / Muted** — honored by every lifecycle notification. Executor questions
  respect the preference too, but fall back to any available channel if the preferred one
  can't deliver (the "do we start?" must arrive).
- **New "My profile" page** (click your email in the top bar): language, notification
  channels, name, password change, and your Telegram activation status/code with
  copy-paste `/activate` instructions. Admins can also set the channel preference from
  the user form.

## 2.27 — 2026-07-07
- **The 5 notification roles now really work as a subscription matrix**: `notify_created`
  fires when an MDT is created, `notify_approved` when it is approved/scheduled,
  `notify_initiated` when the task starts, `notify_completed_ok` / `notify_completed_fail`
  when it closes — by email and Telegram (to linked chats). Works from both the web buttons
  and the executor confirmation links.
- **Stakeholder-scoped notifications**: contacts of a stakeholder only get MDTs that touch
  THEIR services; internal users (no stakeholder) and "sees all MDTs" stakeholders get
  everything.
- **Window validation**: creation rejects past dates, and end time must be after start time.

## 2.26 — 2026-07-07
- **Multi-language UI: English (default), Español and العربية with full RTL.** Language
  switcher (EN | ES | AR) on the top bar of every page; the choice is remembered per user;
  admins can preset a user's language and set the tenant default in Settings. Arabic mirrors
  the whole layout automatically (logical-property CSS).
- **Complete visual redesign** — modern design system: brand tokens, 6 distinguishable MDT
  status colors (with color-blind-safe dots), responsive layout (mobile subnav, scrolling
  tables), keyboard focus outlines, categorized flash messages (green success / red error),
  hero-style login, elevated dashboard cards. Pills no longer wrap mid-word.
- **Telegram activation like v1**: every user gets an activation code (shown on their user
  page); sending `/activate CODE` to the tenant bot links their chat automatically — no more
  manual chat IDs. The 19 v1 codes are imported, so existing codes keep working. A Telegram
  icon in the Users list marks who is already linked.
- Mail: **From display name** setting and **"Send test to"** field for the test email.
- Data migrated from v1 (10.0.254.151): 18 stakeholders, 18 services, 19 contacts with roles,
  Telegram chat IDs and compatible passwords.

## 2.25 — 2026-07-07
- Settings: new **Readiness** box — a live checklist (per tenant) that validates the whole
  notification/executor setup: master switch, email channel complete, Telegram channel
  complete, at least one user with a chat ID, timezone valid. Executor prompts only flow when
  it says ready.
- Telegram: new **"Send test message to me"** button — sends a REAL message to your own chat ID
  with the token in the form (the old button only validated the token). Clear guidance if your
  user has no chat ID yet.

## 2.24 — 2026-07-07
- Deploy: nginx is restarted after every `compose up` so it re-resolves the `web` upstream.
  (Adding the scheduler service shifted web's internal IP and nginx kept proxying to the old
  one → 502 until restarted. Now automatic.)

## 2.23 — 2026-07-07
- **Executor flow (el corazón operativo).** New **Executor** role (`execute_mdt` permission,
  now required to Execute/Close). For every Scheduled MDT with an executor: N minutes before
  the window (per-tenant setting, default 60) the executor gets email + Telegram
  **"Is everything OK for this window?"**; at window start, **"Do we start?"**; on YES the MDT
  moves to Executed, an email goes to everyone with notification roles (**"task started"**),
  and the executor immediately gets **"Finished OK or FAILED?"** — the answer closes the MDT
  with the outcome. All via signed links that work without login (executors can be no-login
  contacts).
- **Every step is timestamped** in the new `mdt_checkpoint` table (RLS) and shown as an
  "Executor flow" timeline on the MDT detail, plus an **Execution time** figure
  (start confirmed → finish reported) — the base for hours-in-MDT reporting.
- New **scheduler** container runs the check every minute (`flask notify-tick`).
- Settings → Notifications: new **Executor pre-check (minutes)** and **Timezone**
  (default Asia/Riyadh) fields. Users now have a **Telegram chat ID** field (get yours from
  @userinfobot); Telegram messages carry inline buttons.

## 2.22 — 2026-07-07
- **Users can now be associated to a Stakeholder** (new optional field + dropdown in the user
  form, new column in the Users list). This is the first brick of the contacts model: a user
  linked to a stakeholder is that organization's contact.
- **Password is now optional when creating a user**: leave it blank to create a
  **contact without login** (shown with a "no login" pill) — they can hold roles like
  Notifications without ever signing in. Setting a password later enables login.

## 2.21 — 2026-07-07
- **Roles now GOVERN the workflow (Fase 1 del backlog funcional).** Every MDT transition checks
  real permissions: Submit needs `request_mdt`, Schedule/approve needs `approve_mdt`,
  Execute/Close need `edit_mdt` (or approver), Cancel needs requester/approver. The `admin`
  permission (and platform admins) always pass. Action buttons only show what YOU can do.
- **Audit trail**: new `mdt_event` table (RLS-protected) records who moved each MDT, when,
  from/to which status, with an optional note — shown as a History section on the MDT detail.
  Creation is recorded too.
- **Closing an MDT now REQUIRES an outcome** (success / unsuccessful) — no more silent NULLs.
- **Physical delete restricted**: only Drafted MDTs, only by admins — anything further along
  must be Cancelled (the record is evidence).
- **Users, Roles and Settings pages are admin-only** (permission `admin`); their links and
  dashboard cards hide for everyone else.
- Boot-time `seed-tenant-admins`: any active tenant with no admin gets the Admin role granted
  to its oldest active user, so nobody gets locked out by the new enforcement.

## 2.20 — 2026-07-07
- **Deploys are now serialized with a server-side lock** (`flock /tmp/mwm_deploy.lock` around
  both the file extraction and the whole bootstrap). Two deploys launched minutes apart used to
  build in parallel and *the one finishing last won* — even if it was the older one — which
  put a stale container back into service (the "v2.18 page after deploying 2.19" episode).
  Queued deploys now wait their turn instead of racing.

## 2.19 — 2026-07-07
- **Login: the platform-admin password is no longer silently reset on every deploy.**
  `seed-platform-admin` runs on each container boot and used to overwrite the existing
  password with the `.env` value — so any password you set survived only until the next
  deploy. It now only creates the user if missing; use `flask set-password` to change it.
- Login field accepts **email or username** again (`type="text"`): platform admins created
  with `create-admin <login>` have username-style logins, which the `type="email"` field
  introduced in 2.16 rejected in the browser.

## 2.18 — 2026-07-07
- Deploy no longer hangs at the end. The server writes a `.deploy_status` file (build result,
  host vs container version, PASS/FAIL) and `deploy.bat` reads that instead of exec-ing into the
  still-booting web container. Server-side version capture is time-boxed, so it can never block.
- Deploy output no longer prints the platform admin password.

## 2.17 — 2026-07-07
- **Real root cause of the intermittent login / "stale page" saga found and fixed.** The RLS
  GUCs (`app.current_tenant`, `app.platform`) were set at **session level** on pooled DB
  connections, so a connection kept the previous request's tenant — or platform mode — alive
  after any mid-request commit (e.g. the lazy role seeding on the Users pages). Under RLS that
  silently returns wrong/empty rows: pages that "look cached", users that appear logged out,
  and a latent cross-tenant data-leak risk. The GUCs are now **transaction-local**
  (`set_config(..., true)`) and re-applied at the start of every DB transaction via a
  SQLAlchemy `after_begin` hook, so no request can ever run under another request's context.
- Session cookie renamed to **`mwm_session`** — any stale `session` cookie still sitting in a
  browser (from v1 or older builds) can no longer shadow a fresh login. Everyone logs in once
  again after this deploy; that is expected.
- Cookies are now `Secure` by default (override with `SESSION_COOKIE_SECURE=0` for plain-http
  dev) and logins last 12 rolling hours instead of "until the browser closes".
- Platform login no longer falls back to matching users across all tenants when the `admin`
  tenant is missing — it fails with a clear message instead.

## 2.16 — 2026-07-07
- Server-side login fixes: new `flask list-users` (shows every user with tenant, active flag, and
  whether a password is set) and `flask set-password <tenant_slug> <email> <password>` (resets a
  password and re-activates the user). Shipped as `list_users.bat` and `set_password.bat`.
- Login label corrected to **Email** — there is no separate username; sign-in is by email only.

## 2.15 — 2026-07-07
- Deploy is now **self-verifying**: a failed server build can no longer hide behind a stale
  container. `bootstrap` always signals completion (even on failure) and reports `BUILD_RESULT`;
  `deploy.bat` prints the build log, container status, web logs, and a host-vs-container version
  `DEPLOY_CHECK=PASS/FAIL` so we always know exactly what is running.
- Login page reloads itself if the browser restores it from back/forward cache (bfcache), so a
  stale snapshot can never be shown to the user.

## 2.14 — 2026-07-07
- Settings: **Test** buttons in Mail and Telegram boxes with on-screen feedback (send a real
  test email; validate the Telegram bot token via getMe). Uses the values currently in the form.

## 2.13 — 2026-07-07
- QW8: per-tenant **Settings** (subnav button) with three boxes — **Mail (SMTP)**, **Telegram**
  (bot token), and **Notifications** (emergency threshold days, advance-notice hours). Stored
  per-tenant in `tenant_setting` with RLS.

## 2.12 — 2026-07-07
- Diagnostic logging (per-request + login) now OFF by default, gated behind MWM_DEBUG_LOG=1.
  Root cause of the recent 'stuck version / intermittent login' was stale BROWSER CACHE from the
  pre-no-cache era, not a server bug (logs proved login worked every time).

## 2.11 — 2026-07-07
- Temp: login diagnostics to pinpoint the direct-tenant-login issue (RLS GUC + user visibility).

## 2.10 — 2026-07-07
- QW7: **Impact catalog (ABM)** — per-tenant catalog of impact types (Logical L1/L2, Physical
  P1/P2/P3, Infra I1/I2/I3, seeded by default and fully editable). MDTs can reference impact
  types on the form. New `impact_type` + `mdt_impact` tables with RLS.
- Prettier platform / workspace / unknown landing pages (centered hero with the brand mark and
  a real Log-in button).

## 2.09 — 2026-07-07
- Hard no-cache on EVERY page: HTTP `no-store` headers (all responses) PLUS `<meta http-equiv>`
  no-cache tags in base.html, and the stylesheet is cache-busted with `?v=<version>`. Every
  template extends base, so all current and future pages inherit it. (Fixes stale console /
  workspace pages that looked like missing data.)

## 2.08 — 2026-07-06
- QW6: **MDT module** (the core) — create / edit / list / view maintenance windows with the
  lifecycle **Drafted -> Submitted -> Scheduled -> Executed -> Closed** (+ Cancel), types
  (scheduled / emergency_scheduled / ad_hoc), affected services, executor, and outcome
  (success / unsuccessful) captured at close. New `mdt` table with its own RLS policy.

## 2.07 — 2026-07-06
- QW5: **Users & roles** (tenant-side) — the tenant admin manages their own users
  (add / edit / activate / delete, password reset) and assigns roles. Default role bundles
  (Admin, Approver, Emergency Approver, Requester, Notifications, Stakeholder Admin) are seeded
  per tenant; a read-only Roles page lists each role's permissions.

## 2.06 — 2026-07-06
- QW4: **Services** module — create / edit / delete services inside a tenant, each optionally
  linked to a stakeholder (dropdown). RLS-scoped; on the dashboard and tenant sub-nav.

## 2.05 — 2026-07-06
- QW3: **Stakeholders** module — create / edit / delete client organizations inside a tenant
  (RLS-scoped), with the "sees all MDTs" flag. Reachable from the dashboard card and a new
  tenant sub-nav.

## 2.04 — 2026-07-06
- CLI `create-admin <login> <password>` for global (platform) admins; the login form now
  accepts a **username or an email**. Helper: `create_admin.bat`.

## 2.03 — 2026-07-06
- QW2: tenant users log into their subdomain and land on a **dashboard** (module cards).
- Platform console: **tenant detail** page — add / remove a tenant's login users (bootstraps
  tenant access; e.g. give `rcrc` its first user).
- **Modern UI refresh**: new design system (topbar with brand mark, cards, tables, pills,
  buttons) — applied across the platform console and tenant pages.
- **No caching anywhere**: `Cache-Control: no-store` on every response — fixes stale pages
  like the old "Unknown workspace".

## 2.02 — 2026-07-06
- Fix 500 on platform/admin login: the RLS GUC `app.current_tenant` was set to an empty
  string when no tenant is in context (admin subdomain), and `''::int` errors in Postgres.
  Now defaults to `'0'` (a safe, non-matching id); platform access still flows via `app.platform`.

## 2.01 — 2026-07-06
- Auth: real login / logout with sessions; login scoped to the platform (admin subdomain)
  or to the tenant of the subdomain.
- Platform console at `admin.mwm.somtik.com`: list, create, enable/disable and delete tenants
  from the web, and set each new tenant's first admin login. Slugs forced lowercase + validated.
- CLI: `create-tenant` now lowercases slugs; added `delete-tenant` and `list-tenants`.
- Dockerfile sets `FLASK_APP=wsgi.py` so `flask ...` works in `docker compose exec`.

## 2.00 — 2026-07-06
- Project kickoff: v2 rebuild of the RCRC maintenance-windows tool as **MWM**, a multitenant
  product served at `mwm.somtik.com` (RCRC Riyadh Bus = first tenant).
- Foundation: Docker + Postgres + Flask (app factory + blueprints), SQLAlchemy 2 models,
  Flask-Migrate / Alembic.
- Multitenancy: shared DB + `tenant_id` on every tenant table, Postgres **Row-Level Security**,
  subdomain-based tenant resolution middleware, platform-admin escape hatch.
- Core models: tenant, stakeholder, service, user, role, permission, user_role_grant.
- Deploy kit: key-based SSH (passwordless), non-superuser DB role for RLS, nginx wildcard-TLS.
v2.76