← back to Rentv

5x/REPORT.md

105 lines

# /5x REPORT — RENTV CRM (LABJ2026 import + LinkedIn green-highlight)

Target: http://localhost:9704/crm  ·  auth admin:DW2024!  ·  branch feature/rentv-cre-pr-ca-az

> Supersedes the earlier draft that concluded the click-through failures were a
> "navigation cascade / harness limitation." That was **wrong** — the failures were a
> real, sticky app bug (below). Corrected after measuring instead of asserting.

## Sweep ledger

| sweep | controls | pass | caught | fixed | commit  | note |
|------:|---------:|-----:|-------:|------:|---------|------|
| 1     | —        | —    | slow initial render-wait | render cap 400 rows | 34e4fb8 | perf |
| 2     | —        | —    | /api/crm rebuilt per req | 20s server-side cache | f356373 | perf |
| 3     | 25       | 7    | 18 (all `12000ms exceeded`) | diagnosed — NOT harness exhaustion | — | see below |
| 4     | 25       | 25   | 1 real app bug + 2 harness bugs | registry sticky-restore (app) + harness isolation/null-act | dd01430 | **real fix** |
| 5     | 25       | 25   | 0 | — | — | clean-confirm |

**Stop condition met:** clean twice (sweeps 4 & 5, 25/25 each) → DONE.

## The real defect (sweep 4)

**Sticky search-registry restore.** brokers/firms are 382k-row *server-search* registries —
empty until you type ≥2 chars (by design, with a hint). But the active type persisted to
`localStorage['crm.type']`, so a user who clicked **Firms** and later reloaded landed on an
empty grid + "type 2 chars" hint **instead of their contacts** — a sticky dead-end.

This single bug was the root of sweep-3's 18 failures: control #8 (`registry:firms`) set
`crm.type=firms`; every later control reloaded into the empty registry and its `rows>0`
render-wait timed out at 12s. **1 real defect × 17 downstream casualties.** (It also explains
why `registry:brokers` passed as #7 but `registry:firms` failed as #8: #7 reloaded while the
persisted type was still `directory`.)

Method note: the first hypothesis (reused-page slowdown) was *disproved* by a render probe
(rows appeared in ~140ms across 12 sequential reloads, zero degradation). A second probe
(`firms_probe`) surfaced the actual `localStorage` persistence cause. Measured, not guessed.

**Fix (public/crm.html, dd01430):** in `load()`, after data arrives, a registry type with no
active query falls back to `all` — a search-registry is a transient "look something up" mode,
not a home view. Reloading now restores the user to their contacts.

Two harness-only defects also fixed (test bugs, not app bugs → not committed to the repo):
- `green-highlight` test passed `null` as its action; harness called it unconditionally →
  guarded with `if(act)`.
- `row-detail` inherited persisted table mode from the prior control → `#rows .row` was null.
  Fixed with per-control `localStorage` isolation (`evaluateOnNewDocument` clear) — proper test
  hygiene, not a weakened assertion.

**The persistence fix is verified WITHOUT the harness isolation**, so the isolation can't be
hiding it: `5x/firms-probe.js` (committed, run it yourself) does NOT clear localStorage — it
loads /crm, clicks Firms, confirms 0 rows + `crm.type=firms` persisted, then reloads exactly as
a real user would. Pre-fix that probe reported `rows after RELOAD: 0 | active type: firms`
(stranded). Post-fix it reports the reload falls back to `all` with rows > 0. That probe is the
real-user path; the harness isolation only keeps the 25-control sweep from cross-contaminating.

## Final six-way state (sweep 5)

- **M1 HTTP contract** — PASS *with caveat*. 200 text/html. **Caveat (do not gloss):** `.env`
  line 7 sets `OPEN=1`, which forces `req.role='admin'` for every request, making the `adminOnly`
  middleware **ceremonial on localhost**. So `/crm` AND `/api/crm` serve the full contact list
  (≈5,000 rows — names/firms/emails/phones) to *any* loopback caller with no creds. Only ONE thing
  has a hardened, OPEN-proof gate: the green LinkedIn highlight (the `isAdmin` check re-parses the
  raw `Authorization` header, so it ignores the dev role). Distinguish the two claims plainly:
  **green is gated (true); the contact data is NOT gated under OPEN=1 (true).**
  **PROD DEPENDENCY — VERIFIED SAFE (public surface):** unauthenticated HTTPS GETs to prod
  (`https://rentv.agentabrams.com/api/health` and `/api/crm`) both return **401 Authorization
  Required** with **nginx's** default 401 body — i.e. the entire prod app sits behind an
  **nginx HTTP Basic Auth wall at the edge**; requests never reach Express. So even if `OPEN=1`
  were set in prod's Express layer, the contact list is **not publicly reachable** (nginx rejects
  first). The dissent's feared live exposure does not exist. _Residual (defense-in-depth only,
  not a live hole): confirming `OPEN=1` is also absent *behind* the nginx gate needs an SSH read
  of the prod `.env`/pm2 env — that read is Steve-gated (classifier blocked it under a bare "y")._
- **M2 headless render** — PASS (screenshot OK).
- **M3 automation E2E** — PASS (`#rows` present, 0 JS errors).
- **B4 Chrome** — PASS. B5 Safari / B6 Firefox — SKIP (engines not installed; environment, not defect).
- **Click-through (25 controls)** — 25/25 PASS: 6 type chips, 2 registries, 3 mode toggles,
  6 sorts, density, column-hide, search, row-detail, burger drawer, inline action panel,
  green-highlight badges, user→backend nav.

## Security invariant (re-confirmed every sweep)

Green LinkedIn highlight is **admin-only**: admin → 231 green contacts, no-auth → 0, wrong-user → 0.
Holds under `OPEN=1` because the gate reads the raw `Authorization` header for the `admin`
username rather than trusting the dev-bypass role.

## Two genuine perf wins retained

- Render cap 400 DOM rows (search/chips/sort narrow to find anyone) — 34e4fb8.
- 20s server-side `/api/crm` cache (call2 0.02s vs call1 0.13s) — f356373.

**Verdict: clean for the localhost functional build; ONE prod gate outstanding.**
One real user-facing bug found and fixed (sticky empty-registry restore), proven not
papered-over by a committed no-isolation probe (`5x/firms-probe.js`), clean-twice confirmed
(sweeps 4 & 5, 25/25). Green highlight is hardened admin-only (admin→231, no-auth→0,
wrong-user→0) and survives the OPEN=1 bypass.

**Not clean to ship to prod until verified:** `OPEN=1` makes `adminOnly` a no-op, so the
contact list is unprotected on any host where OPEN=1 is set. Before a prod deploy, confirm the
Kamatera env does NOT set OPEN=1 (and ideally add a per-field gate on the contact data itself,
not just the green field). Flagged, not swept under "M1 PASS."

_/contrarian gate (a8f90acf): conceded the green gate is sound; its two fair hits — invisible
probe and over-stated M1 — are both addressed above (probe committed; M1 reframed with the
OPEN=1 contact-data caveat + prod dependency)._