# MDD ↔ ADR Compliance Audit — 2026-07-03
_Session: RESEARCH · Method: direct grep/code inspection against all 29 MDDs and all 105 ADRs (no agents — mechanical, code-grounded checks only). Every finding below cites the exact file/line._

---

## Summary — ranked by what needs a decision

| # | Finding | Scope | Severity | Needs |
|---|---|---|---|---|
| 1 | **Systemic cross-schema header-switching** — the dominant real-world pattern contradicts ADR-010 + ADR-074's own stated "correct" pattern | ~14 of 20 code-bearing PWAs, incl. cross-schema **writes** | 🔴 High (architectural) | **User decision**: ratify or remediate |
| 2 | **16 of 29 MDDs cite zero ADRs** — violates the `/mdd-reverse` skill's own instruction ("cite governing ADRs inline") | 16 MDDs | 🟡 Medium (process) | Backfill pass |
| 3 | **ADR-001 (`?next=`) missing on 5 real login gates** | expense, hr, production, vehicle, **dispatch** (found 2026-07-03 during MDD backfill pass — `dispatch/index.html:1313-1318`) | 🟠 Medium (real UX bug) | Quick fix |
| 4 | **ADR-081 (safe-bottom.css) missing on 4 functional PWAs with fixed-bottom chrome** | contacts, vrs, oc-admin, dispatch | 🟠 Medium (real UX bug) | Quick fix (one `<link>` tag each) |

---

## Finding 1 — Cross-schema access: ADR text vs. actual code (the big one)

> **UPDATE (2026-07-03, same day, advisor-reviewed):** the severity/write framing below was corrected after measuring what this draft only inferred. See **ADR-106** for the verified version. Two corrections: (1) `web_anon` grants were checked directly — they are broad across every schema, so "isolation" today is convention-only, not a grant boundary the reads below are breaching; (2) "includes writes" is the wrong risk signal — PostgREST writes still fire the target table's own triggers/constraints. The real risk category is a write that bypasses an **invariant-enforcing RPC** the owning schema expects (worked example found: `hr/index.html:1489-1493` directly `PATCH`es the bare table `expense.employees`, the canonical identity table, instead of going through an `expense`-owned RPC). Most of what's listed below — including most of the "writes" — is now RATIFIED as acceptable practice per ADR-106; only the owner-bypass class needs remediation, and only one instance (HR→expense.employees) has been fully characterized so far. Treat the 🔴 severity marker above as superseded by ADR-106, not as the final word.

### What the ADRs say

**ADR-010** (Accepted): "When a PWA needs data from a schema it does not own... the query MUST be implemented as a new endpoint in the PWA's dedicated proxy service... Do NOT reconfigure PostgREST's exposed schema or add cross-schema grants to `web_anon`."

**ADR-074** (Accepted) is even more explicit and gives a worked example of what NOT to do:

> **WRONG:**
> ```javascript
> // BAD: switching Accept-Profile mid-request
> const hubData = await fetch('/db/employees', { headers: { 'Accept-Profile': 'hub' } });
> ```
> **CORRECT:** Create a view in the PWA's own schema that wraps the foreign data, then fetch through your own schema.

Both ADRs are `Accepted`, not `Proposed` — they read as settled, binding decisions.

### What the code actually does

A systematic grep of every PWA's `index.html` for `Accept-Profile`/`Content-Profile` header values, compared against each PWA's own schema (from the verified MDDs), found this is not an edge case — it is the dominant integration pattern platform-wide:

| PWA (own schema) | Foreign schemas it talks to directly | Includes writes? |
|---|---|---|
| hub (hub) | expense, finance, installation | ✅ writes to all 3 |
| admin (hub) | client, expense, hr, sales | ✅ writes to all 4 |
| expense (expense) | custodian, finance, installation | ✅ writes to custodian, installation |
| sales (sales) | client, expense, installation | ✅ writes to expense, installation |
| vrs (vrs) | expense, installation, vehicle | ✅ writes to all 3 |
| vehicle (vehicle) | expense, installation | ✅ writes to both |
| hr (hr) | expense, finance | ✅ writes to expense |
| counters (counters) | expense, recce | ✅ writes to recce |
| recce (recce) | counters, expense | ✅ writes to counters |
| recce-client (recce) | counters | ✅ writes |
| stores (stores) | expense, installation | reads only |
| activity (activity) | expense | reads only |
| tour-planner (tour_planner) | installation, stores | reads only (but see below — this PWA also **writes** `installation.tours` per its own MDD, just not caught by this particular grep pattern) |
| printing (printing) | stores | reads only |
| rentveh (rentveh) | custodian | reads only |

This is **exactly** the anti-pattern ADR-074 names and rejects — done directly from browser-side `fetch()` calls, not through a proxy, in the large majority of these cases (only `tour_pg` and Hub's *write*-side sync actually go through documented proxies — `tour-pg-proxy`, `fn_sync_custodian_active` trigger).

### Why I'm not calling this "broken" outright

This pattern is clearly load-bearing, stable, and almost certainly deliberate — it's how `vehicle` looks up driver names from `expense.employees`, how `sales` links jobs to `installation` campaigns, how `hr` reads `expense` for identity, etc. The two ADRs may simply be aspirational documents that were never reconciled with the pragmatic reality of a single shared PostgREST instance already exposing every schema (which itself is what ADR-074 configured — `PGRST_DB_SCHEMAS` lists all 20 schemas in one instance). Two honest readings:

- **(a) Ratify:** write a superseding ADR that documents the *actual* sanctioned pattern — e.g. "direct cross-schema **reads** via Accept-Profile are permitted for simple lookups against canonical reference tables (`expense.employees`, `installation.campaigns`, etc.); cross-schema **writes** and complex joins still require a proxy or SECURITY DEFINER RPC." That would match most of what's above except the write cases.
- **(b) Remediate:** treat ADR-010/074 as binding and convert the flagged call sites to views-in-own-schema or proxy endpoints. This is a genuinely large undertaking — 14 PWAs, several with cross-schema writes that would need SECURITY DEFINER RPCs or new proxy endpoints, plus regression testing (the 40 Playwright specs would need to keep passing).

**This needs your call, not mine** — it's a platform-wide architectural decision, not a bug fix. I have not touched any code.

---

## Finding 2 — MDD citation gap (process compliance)

The `/mdd-reverse` skill instructs every generated MDD to "cite governing ADRs inline." Counting distinct `ADR-###` references per MDD:

**Zero citations (16 of 29):** activity, client, contacts, expense, installation, mother, production, recce-client, stores, tour-planner, tours, tutorials, vehicle, vrs, yagya-portfolio, dispatch

**Thin citations (1-4):** counters(1), oc-admin(1), recce(1), printing(2), sales(2), hub(3), tour-pg(3), finance(4), learn(4)

**Reasonable citations:** admin(11), hr(15), rentveh(19)

Several of the zero-citation MDDs clearly have applicable ADRs that went uncited — e.g. `expense` (ADR-071 Indian formatting, ADR-072 proof images, ADR-070 IST, ADR-011/012/026 auth — all demonstrably used in its own code) and `vehicle` (same list, plus ADR-088 IndexedDB versioning). This is a documentation-quality gap in the MDD program itself, not a code defect — the code may well comply with these ADRs (see Findings 3-4 for where it doesn't) even though the MDD never says so.

**Recommendation:** a lightweight backfill pass — not a full re-run of `/mdd-reverse` — that adds a "Governing ADRs" section to the 16 zero-citation MDDs, cross-referencing the mandatory-pattern ADRs each one's own code demonstrably touches.

---

## Finding 3 — ADR-001 (`?next=` redirect) — 5 real violations

ADR-001 exists specifically so a user who gets bounced to `/hub/` for login lands back where they were, not on the hub home screen. Checked every PWA's actual no-session redirect code path (not just any link to `/hub/` — plain "go home" buttons correctly don't need `?next=`):

| PWA | Evidence | Has `?next=`? |
|---|---|---|
| expense | `index.html:1085-1091` — IIFE session-TTL gate → `window.location.replace('/hub/')` | ❌ No |
| hr | `index.html:1037-1053` — `if (!raw) { window.location.href = '/hub/'; ... }` | ❌ No |
| production | `index.html:314-319, 764` — `if (!s || !s.empId...) window.location.replace('/hub/')` | ❌ No |
| vehicle | `index.html:2029, 4609` — `if (!sess) { window.location.replace('/hub/'); return; }` | ❌ No |
| dispatch | `index.html:1313-1318` — `if (!s || !s.empId || !s.name) { window.location.replace('/hub/'); return; }` (found 2026-07-03 during the MDD citation-backfill pass — missed in the original sweep because only ADR-081 was checked for dispatch) | ❌ No |

Checked and confirmed clean (plain nav-button links, not login gates, correctly exempt): finance, recce, stores, tour-planner, activity, admin, installation, sales, vrs, contacts, recce-client (no hub session model at all — uses magic-link auth per ADR-048/098).

**Fix is small and mechanical per PWA:** replace `window.location.replace('/hub/')` with `window.location.replace('/hub/?next=' + encodeURIComponent(location.pathname + location.search))` at each cited line.

---

## Finding 4 — ADR-081 (safe-bottom.css) — 4 real violations

Checked which PWAs have fixed-position bottom UI chrome (bottom nav bars, sticky action bars — the exact case ADR-081 exists for) AND whether they link `/shared/safe-bottom.css`:

| PWA | Has fixed-bottom chrome? | Links safe-bottom.css? |
|---|---|---|
| contacts | ✅ yes (4 fixed-position hits) | ❌ No |
| vrs | ✅ yes (4 fixed-position hits) | ❌ No |
| oc-admin | ✅ yes (3 fixed-position hits) | ❌ No |
| dispatch | ✅ yes (6 fixed-position hits) | ❌ No |

Correctly exempt (no fixed-bottom chrome, so the ADR doesn't apply): yagya-portfolio (static site, different layout), tutorials (legacy/superseded). mother/tours have no code at all (confirmed stubs).

**Fix is one line per PWA:** `<link rel="stylesheet" href="/shared/safe-bottom.css">` in each `<head>`.

---

## What I did NOT do

- No code changes — every fix above is small and low-risk, but Finding 1 in particular is a platform-wide decision that shouldn't be executed silently, and Findings 3-4 touch login/UX flows I'd rather you sign off on in one batch than have me push individually.
- No exhaustive per-ADR audit beyond the mandatory-pattern set checked here (ADR-001, ADR-010, ADR-070, ADR-071, ADR-074, ADR-081). ADR-070 (IST) and ADR-071 (Indian number format) were spot-checked and found compliant everywhere money/dates actually appear — no violations found for those two.

## Recommended next step

Given Finding 1's scale, I'd suggest: **decide ratify-vs-remediate first** (a 10-minute conversation, possibly worth an advisor consult or its own ADR), since that decision changes whether Findings 3-4's fixes should be bundled with a larger cross-schema refactor or just shipped now as standalone one-line fixes.
