[object Object]

← back to Rentv

5x: address contrarian gate — commit firms-probe (no-isolation, real-user path), reframe M1 with OPEN=1 contact-data caveat + prod dependency

bea77e71427c769b45ecb944845cf5ccd0da0b90 · 2026-08-12 19:19:27 -0700 · steve

Files touched

Diff

commit bea77e71427c769b45ecb944845cf5ccd0da0b90
Author: steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 19:19:27 2026 -0700

    5x: address contrarian gate — commit firms-probe (no-isolation, real-user path), reframe M1 with OPEN=1 contact-data caveat + prod dependency
---
 5x/REPORT.md      | 39 ++++++++++++++++++++++++++++++++-------
 5x/firms-probe.js | 28 ++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/5x/REPORT.md b/5x/REPORT.md
index e948ab4f..cde5497b 100644
--- a/5x/REPORT.md
+++ b/5x/REPORT.md
@@ -44,14 +44,27 @@ Two harness-only defects also fixed (test bugs, not app bugs → not committed t
   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 app-level persistence fix stays independently verified
-  by `firms_probe`, so isolation does not paper over it.
+  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 (200 text/html). Honest note: `/crm` serves the page *shell* to
-  no-creds because `OPEN=1` dev-bypass is set in `.env`. The **sensitive layer is server-gated**:
-  the green LinkedIn highlight requires a genuine `admin` credential.
+- **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:** the Kamatera prod deploy (claude-rentv's worktree) must NOT set `OPEN=1`,
+  or `adminOnly` no-ops there too and the contact list is exposed publicly. This must be verified
+  before any prod ship — it is out of scope for a localhost /5x and is flagged, not claimed clean.
 - **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).
@@ -70,5 +83,17 @@ username rather than trusting the dev-bypass role.
 - 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.** One real user-facing bug found and fixed (sticky empty-registry restore),
-proven not papered-over (independent probe), clean-twice confirmed, green stays admin-only.
+**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)._
diff --git a/5x/firms-probe.js b/5x/firms-probe.js
new file mode 100644
index 00000000..05177864
--- /dev/null
+++ b/5x/firms-probe.js
@@ -0,0 +1,28 @@
+const puppeteer=require(process.env.HOME+'/.npm-global/lib/node_modules/puppeteer');
+const AUTH='Basic '+Buffer.from('admin:DW2024!').toString('base64');
+(async()=>{
+  const b=await puppeteer.launch({headless:'new',executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',args:['--no-sandbox']});
+  const p=await b.newPage(); await p.setExtraHTTPHeaders({Authorization:AUTH}); await p.setViewport({width:1500,height:950});
+  await p.goto('http://localhost:9704/crm',{waitUntil:'domcontentloaded',timeout:30000});
+  await p.waitForFunction("document.querySelectorAll('#rows .row,.tbl tbody tr').length>0",{timeout:12000});
+  const before=await p.evaluate(()=>document.querySelectorAll('#rows .row,.tbl tbody tr').length);
+  console.log('initial rows:',before);
+  // is there a firms button?
+  const hasFirms=await p.evaluate(()=>!!document.querySelector('button[data-type="firms"]'));
+  console.log('firms button exists:',hasFirms);
+  if(hasFirms){
+    await p.evaluate(()=>document.querySelector('button[data-type="firms"]').click());
+    await new Promise(r=>setTimeout(r,3000));
+    const after=await p.evaluate(()=>document.querySelectorAll('#rows .row,.tbl tbody tr').length);
+    const ls=await p.evaluate(()=>JSON.stringify(Object.keys(localStorage)));
+    console.log('rows after firms click:',after);
+    console.log('localStorage keys:',ls);
+  }
+  // now reload and see what state persists
+  await p.goto('http://localhost:9704/crm',{waitUntil:'domcontentloaded',timeout:30000});
+  await new Promise(r=>setTimeout(r,1500));
+  const afterReload=await p.evaluate(()=>document.querySelectorAll('#rows .row,.tbl tbody tr').length);
+  const onchip=await p.evaluate(()=>{const c=document.querySelector('.chip.on,[data-type].on');return c?c.getAttribute('data-type'):'none';});
+  console.log('rows after RELOAD (post-firms):',afterReload,'| active type:',onchip);
+  await b.close();
+})().catch(e=>{console.error('FATAL',e.message);process.exit(1);});

← 26d46a6e 5x: corrected report — sticky search-registry restore was th  ·  back to Rentv  ·  auto-data-snapshot: 2026-08-12T19:37:30 (7 data files) — dat ffb6b4a4 →