49a0778a53
#787 registered at 18:35 CT, 22 minutes after the first join-flow fix, and still had no profile. Two causes, both fixed: 1. messages.verifyChallenge resolved the wallet through chain.memberIdByAccount, which reads the CACHED index. Seconds after a registration that wallet is not in it, so the signature was rejected with "No RM Circle position is registered to this wallet". It now accepts an idHint (the position id from the member's own registration receipt) and, on a cache miss, reads that id live from the contract via chain.verifyMember, minting only when the contract says this exact wallet owns it. That is a stronger proof than the cache, not a weaker one. Now async; the single call site awaits. 2. join-now.js fired the sign-in and a 4.5s redirect in parallel, so the page could navigate away while the wallet was still showing the signature prompt, and it did not wait for submit-id (which runs the live verifyMember server-side that seeds the index). It now awaits the report, passes the receipt id, and redirects only once the signature settles, with a 120s bailout. qa/signin-fallback.mjs (7 assertions) proves the cold-index path with real secp256k1 signatures and covers the abuse cases: a hint for a position the wallet does not own is refused, and a signature from another wallet is refused. Existing suites still pass: profiles-unit 28, gate-e2e 47. 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.