qa/harness-server.js boots the real server with chain reads stubbed (COLD=1 reproduces the cached-index state that left #787 without a profile). qa/join-flow-e2e.mjs drives the real /join-now page with a fake wallet producing genuine secp256k1 signatures. Three scenarios pass: signs on a cold index (9) - the gate appears on their own dashboard; refuses to sign (10) - REGRESSION, the join still completes and redirects with the page usable; signs on a warm index (9). Full set green: profiles-unit 28, signin-fallback 7, gate-e2e 47, join-flow 9/10/9, plus the live shared-link check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RM Circle QA
Member profile gate
# unit: profiles.js in isolation (seeding, username rules, code flow, persistence)
node qa/profiles-unit.mjs
# end to end: the real UI with real inbox sessions
TMP=/d/tmp/rmc-e2e-data && rm -rf $TMP && mkdir -p $TMP
ssh root@coolify.saasy.top "docker exec \$(docker ps -q --filter name=kr445fqc) cat /app/data/config.json" > $TMP/config.json
echo '{}' > $TMP/sponsors.json
echo '{"49":{"email":"seeded49@example.com","ts":"x"}}' > $TMP/member-alerts.json
node -e "const m=require('./messages.js'); m.init({dataDir:process.argv[1],chain:require('./chain.js'),isProd:false}); require('fs').writeFileSync(process.argv[1]+'/tokens.txt', m.mintSession(21)+'\n'+m.mintSession(49));" $TMP
PORT=3399 DATA_DIR=$TMP ADMIN_PASSWORD=localtest node server.js &
LOCAL=http://127.0.0.1:3399 TOKEN=$(sed -n 1p $TMP/tokens.txt) TOKEN2=$(sed -n 2p $TMP/tokens.txt) node qa/gate-e2e.mjs
Covers: the gate fires on the owner's own page only, cannot be dismissed (Escape, backdrop, no close button), username and email validation, the code flow including a wrong code, completion landing on the dashboard tab, the profile card and in-place editing (dismissable), the seeded email pre-filling at step 2, visitors on every ID-keyed shared link seeing no gate and no 401, and phone-width layout.
devCode is returned by /api/public/profile/email-start only when NODE_ENV !== 'production',
which is what lets the test read the code. The live container runs with NODE_ENV=production.
Join flow (the money path)
Drives the REAL /join-now page with a fake wallet that produces genuine secp256k1 signatures, against
a server whose chain reads are stubbed by qa/harness-server.js. COLD=1 reproduces the state that
left #787 without a profile: the cached index does not yet know the brand-new position.
node -e "const c=require('crypto'),s=require('./vendor/secp256k1.js'),{keccak256}=require('./vendor/sha3.js');s.utils.hmacSha256Sync=(k,...m)=>{const h=c.createHmac('sha256',Buffer.from(k));m.forEach(x=>h.update(Buffer.from(x)));return Uint8Array.from(h.digest())};const p=c.randomBytes(32),pub=s.getPublicKey(p,false);require('fs').writeFileSync('D:/tmp/rmc-qa-wallet.json',JSON.stringify({priv:p.toString('hex'),addr:'0x'+keccak256(Buffer.from(pub.slice(1))).slice(-40)}))"
TMP=/d/tmp/rmc-jd && rm -rf $TMP && mkdir -p $TMP && echo '[]' > $TMP/sponsors.json
ssh root@coolify.saasy.top "docker exec \$(docker ps -q --filter name=kr445fqc) cat /app/data/config.json" > $TMP/config.json
TEST_ADDR=<addr> TEST_ID=9001 COLD=1 PORT=3399 DATA_DIR=$TMP ADMIN_PASSWORD=localtest node qa/harness-server.js &
LOCAL=http://127.0.0.1:3399 TEST_ADDR=<addr> TEST_PRIV=<priv> TEST_ID=9001 SCENARIO=sign node qa/join-flow-e2e.mjs
LOCAL=http://127.0.0.1:3399 TEST_ADDR=<addr> TEST_PRIV=<priv> TEST_ID=9002 SCENARIO=refuse node qa/join-flow-e2e.mjs
SCENARIO=refuse is the regression that matters most: a member who declines the signature must still
complete the join and reach their dashboard. Never ship a join-flow change without it passing.
Gotchas: seed sponsors.json as [] (an object 500s), and the fake wallet auto-connects so
#connectBtn is hidden. Counters live in sessionStorage because the page redirects.