Skip to content

Commit fa96e2e

Browse files
Merge pull request #226 from creator35lwb-web/chore/v0.5.37-version-bump
feat(rate-limit): v0.5.37 Tier Clarity — branch 429 CTA on uuid_status
2 parents 9e01799 + e524672 commit fa96e2e

15 files changed

Lines changed: 128 additions & 26 deletions

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ Full version history also available at [verifimind.ysenseai.org/changelog](https
88
99
---
1010

11+
## v0.5.37 - Tier Clarity (May 26, 2026)
12+
13+
Branches the 429 rate-limit CTA so the response fits *why* the caller is anonymous, and surfaces `uuid_status` for diagnosis. Driven by a tier-setup audit (findings T1–T6).
14+
15+
### What changed
16+
- **429 response body now branches on `uuid_status` (`absent` | `invalid` | `valid`):**
17+
- *No UUID header* → acquisition CTA: register a free Scholar UUID (30/60s + BYOK + dashboard), with the Privacy-Doctrine-v1.0 line and a founder/feedback note.
18+
- *UUID header present but invalid***recovery** hint (`VERIFIMIND_UUID` unset / `your-uuid-here` placeholder → see `/setup`) instead of wrongly pitching registration to someone who already has a UUID.
19+
- `uuid_status` added to the 429 JSON body and the rate-limit warning log (observability for misconfigured-Scholar detection — AY funnel signal).
20+
- CTA logic extracted to a pure `_build_rate_limit_cta()` helper with 4 new unit tests.
21+
- Version bump 0.5.36 → 0.5.37 (both `SERVER_VERSION` surfaces + 9 test files); `server.json` 3.13.0 → 3.14.0.
22+
23+
### Why
24+
A tier-setup audit — prompted by a Scholar-tier user being rate-limited as Anonymous — found: the rate limiter resolves tier *solely* from the `X-VerifiMind-UUID` header (T1); the downgrade to Anonymous is silent (T2); the tool-response `tier` field (from `tier_gate`, = "not Pioneer") contradicts the rate-limiter tier (T5); and the rate limiter reads an **empty** `ea_registrations` collection for Pioneer quota while real registrations live in `early_adopters` (T6 — Pioneer rate tier effectively dead). v0.5.37 ships the user-facing half (recovery CTA + diagnosis). The deeper reconciliation (T3/T6 — single source of truth for caller tier; fix the collection mismatch) is routed to T (CTO) in a forensic audit report — see PRIVATE `.macp/handoffs/`.
25+
26+
### Evidence
27+
AY/AZ Report 092 (May 21–24) showed active anonymous builders hitting the IP-tier wall with 0 registrations — the exact cohort the branched CTA targets.
28+
29+
---
30+
1131
## v0.5.36 - Changelog Endpoint Redirect (May 21, 2026)
1232

1333
Single-sources the changelog to end dual-maintenance drift.

SERVER_STATUS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
# VerifiMind-PEAS Server Status
22

3-
**Last Updated:** May 13, 2026
3+
**Last Updated:** May 26, 2026
44

55
---
66

77
## Current Status: Operational
88

9-
**v0.5.33 "Changelog Hygiene" — deployed May 13, 2026**
9+
**v0.5.37 "Tier Clarity" — deployed May 26, 2026**
1010

11-
The VerifiMind MCP server is fully operational. All security gates passed.
11+
The VerifiMind MCP server is fully operational. All security gates passed. (Per-version public detail now lives in [GitHub Releases](https://github.com/creator35lwb-web/VerifiMind-PEAS/releases) since the `/changelog` redirect in v0.5.36.)
1212

1313
- 13 MCP tools (4 core Trinity + 6 template management + 3 coordination) — **all free for everyone**
14+
- **Tier Clarity (v0.5.37)**: 429 rate-limit CTA now branches on `uuid_status` — a *misconfigured Scholar* (UUID header present but invalid) gets a recovery hint (`VERIFIMIND_UUID` / `/setup`), while a true anonymous caller gets the register-for-Scholar acquisition CTA (+ BYOK + dashboard, Privacy-Doctrine-v1.0 line, founder/feedback note). `uuid_status` surfaced in the body + warning log. Shipped from a tier-setup audit (T1–T6); the T3/T6 reconciliation (rate-limiter reads empty `ea_registrations` vs real `early_adopters`; single source of truth for caller tier) routed to T (CTO) — May 26, 2026
1415
- **Changelog Hygiene (v0.5.33)**: Retroactively sanitized public `/changelog` to remove specific blocked-IP addresses from v0.5.30 and v0.5.32 entries; matches v0.5.22 / v0.5.26 disclosure pattern. Full forensics preserved in internal `CHANGELOG.md`, PR bodies, and commit history. Added disclosure-policy header to internal CHANGELOG. PR# links added to public v0.5.30 / v0.5.32 entries — May 13, 2026
1516
- **Secret Scanner Block + SonarCloud P1 (v0.5.32)**: 7th IP added to application-layer blocklist — credential/secret enumeration scanner, 788 req single burst on May 12 (probed `.env` variants, `.git/*`, `.terraform.*`, `.stripe/`, `?phpinfo=1`, CI configs). 77% caught by rate limiter; zero leak (4 served 200 = safe root response only). SonarCloud P1 cleanup: extracted `MCP_ENDPOINT_PATH`/`MCP_SERVER_URL`/`MCP_REMOTE_QUICKSTART` constants (removed 13 duplicate literals); refactored `http_exception_handler` cognitive complexity 23 → ≤15; CodeQL `py/empty-except` × 2 resolved; logger.exception() in registration 500 path — May 13, 2026
1617
- **SonarCloud P0 (v0.5.31)**: Resolved 14 SonarCloud Vulnerabilities + 15 BLOCKER severity items per XV's May 12 audit. Workflow permissions scoped to job level; TLS 1.2 explicit minimum; broken `__all__` in templates/library removed; 8 false-positive suppressions with NOSONAR + justification comments; deprecated `datetime.utcnow()` replaced. Expected impact: Security count 14 → 1, BLOCKER 15 → 0 — May 13, 2026

mcp-server/http_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
mcp_app = mcp_server.http_app(path='/', transport='streamable-http')
6565

6666
# Server version
67-
SERVER_VERSION = "0.5.36"
67+
SERVER_VERSION = "0.5.37"
6868

6969
# MCP endpoint constants — single source of truth for URL/path strings used in
7070
# JSON responses, quickstart commands, and HTML setup pages. Extracted in v0.5.32

mcp-server/src/verifimind_mcp/middleware/rate_limiter.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,41 @@ def _resolve_uuid_tier(uuid: str) -> str:
7575
return tier
7676

7777

78+
def _build_rate_limit_cta(tier: str, uuid_status: str) -> Tuple[Optional[str], Optional[str]]:
79+
"""Build the (upgrade_hint, from_the_builder) pair for a 429 body.
80+
81+
Branches on why the caller is anonymous so the copy fits the situation
82+
(tier-audit findings T4/T5, v0.5.37):
83+
- tier != "anonymous" → (None, None) no pitch to registered users
84+
- anonymous, uuid invalid → RECOVERY hint "your UUID isn't valid, fix VERIFIMIND_UUID"
85+
- anonymous, uuid absent → ACQUISITION hint "register a free Scholar UUID"
86+
The founder/feedback line accompanies any anonymous-tier 429.
87+
"""
88+
if tier != "anonymous":
89+
return None, None
90+
if uuid_status == "invalid":
91+
upgrade_hint = (
92+
"A VerifiMind UUID was received but is not valid, so this request is on the "
93+
"Anonymous tier (10 req/60s per IP). Check that VERIFIMIND_UUID is set to your "
94+
"real UUID (not the 'your-uuid-here' placeholder) in your MCP config. "
95+
"Setup help: https://verifimind.ysenseai.org/setup"
96+
)
97+
else: # absent — no UUID header at all
98+
upgrade_hint = (
99+
"Anonymous tier limit reached (10 req/60s per IP). Register a free Scholar UUID "
100+
"for 30 req/60s + BYOK + a usage dashboard: https://verifimind.ysenseai.org/register "
101+
"— your UUID is only a quota key; BYOK keys are never logged and registration adds "
102+
"no public identification (Privacy Doctrine v1.0). "
103+
"MCP config: https://verifimind.ysenseai.org/setup"
104+
)
105+
from_the_builder = (
106+
"VerifiMind is built by a solo dev keeping the lights on. "
107+
"Features & feedback welcome → "
108+
"https://github.com/creator35lwb-web/VerifiMind-PEAS/discussions"
109+
)
110+
return upgrade_hint, from_the_builder
111+
112+
78113
class RateLimitStore:
79114
"""In-memory rate limit tracking with automatic cleanup. Sliding window."""
80115

@@ -215,19 +250,25 @@ async def dispatch(self, request: Request, call_next):
215250

216251
client_ip = get_client_ip(request)
217252

218-
# Resolve tier from X-VerifiMind-UUID header
253+
# Resolve tier from X-VerifiMind-UUID header.
254+
# Track WHY a request is anonymous (uuid_status) so the 429 body can tell a
255+
# misconfigured Scholar (header present but invalid) apart from a true
256+
# anonymous caller (no header) — tier-audit findings T1/T2/T5, v0.5.37.
219257
uuid_header = request.headers.get("x-verifimind-uuid", "").strip()
220258
tier = "anonymous"
221259
active_limit = TIER_LIMITS["anonymous"]
260+
uuid_status = "absent" # absent | invalid | valid
222261

223262
if uuid_header:
263+
uuid_status = "invalid" # present until proven valid
224264
try:
225265
from verifimind_mcp.utils.uuid_tracer import is_valid_uuid
226266
if is_valid_uuid(uuid_header):
227267
tier = _resolve_uuid_tier(uuid_header)
228268
active_limit = TIER_LIMITS[tier]
269+
uuid_status = "valid"
229270
except Exception:
230-
pass # invalid header → fall back to anonymous
271+
pass # invalid header → fall back to anonymous (uuid_status="invalid")
231272

232273
if tier == "anonymous":
233274
allowed, retry_after, limit_type = _rate_limit_store.check_and_record(client_ip)
@@ -238,23 +279,26 @@ async def dispatch(self, request: Request, call_next):
238279

239280
if not allowed:
240281
logger.warning(
241-
"Rate limited: tier=%s key=%s type=%s path=%s retry_after=%ss",
242-
tier, (uuid_header[:8] if uuid_header else client_ip), limit_type,
243-
request.url.path, retry_after,
282+
"Rate limited: tier=%s uuid_status=%s key=%s type=%s path=%s retry_after=%ss",
283+
tier, uuid_status, (uuid_header[:8] if uuid_header else client_ip),
284+
limit_type, request.url.path, retry_after,
244285
)
286+
# Branch the CTA so a misconfigured Scholar (UUID present but invalid) gets a
287+
# RECOVERY hint, while a true anonymous caller gets the acquisition pitch —
288+
# tier-audit findings T4/T5, v0.5.37.
289+
upgrade_hint, from_the_builder = _build_rate_limit_cta(tier, uuid_status)
245290
return JSONResponse(
246291
status_code=429,
247292
content={
248293
"error": "rate_limit_exceeded",
249294
"message": f"Too many requests. Please try again in {retry_after} seconds.",
250295
"tier": tier,
296+
"uuid_status": uuid_status,
251297
"limit": active_limit,
252298
"limit_type": limit_type,
253299
"retry_after": retry_after,
254-
"upgrade_hint": (
255-
"Register at verifimind.ysenseai.org/register for Scholar tier (30 req/60s). "
256-
"Pioneer tier: 100 req/60s."
257-
) if tier == "anonymous" else None,
300+
"upgrade_hint": upgrade_hint,
301+
"from_the_builder": from_the_builder,
258302
"documentation": "https://github.com/creator35lwb-web/VerifiMind-PEAS#rate-limits",
259303
},
260304
headers={

mcp-server/src/verifimind_mcp/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
# v0.4.3 — System Notice: broadcast messages to all MCP users via env var
4242
_RAW_SYSTEM_NOTICE = os.environ.get("SYSTEM_NOTICE", "")
43-
SERVER_VERSION = "0.5.36"
43+
SERVER_VERSION = "0.5.37"
4444

4545
# Mock mode transparency — shown in every tool response when no real inference is available
4646
MOCK_MODE_WARNING = (

mcp-server/tests/unit/test_p0_tool_manifest_audit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,4 @@ class TestServerVersion:
279279

280280
def test_server_version_is_0522(self):
281281
import http_server
282-
assert http_server.SERVER_VERSION == "0.5.36"
282+
assert http_server.SERVER_VERSION == "0.5.37"

mcp-server/tests/unit/test_v050_foundation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def test_no_smithery_decorator_in_server_py(self):
6767
def test_server_version_is_0513(self):
6868
"""SERVER_VERSION must be bumped to 0.5.16 (Scholar Incentives — P1-A/P1-C)."""
6969
from verifimind_mcp.server import SERVER_VERSION
70-
assert SERVER_VERSION == "0.5.36", (
71-
f"Expected 0.5.36, got {SERVER_VERSION}"
70+
assert SERVER_VERSION == "0.5.37", (
71+
f"Expected 0.5.37, got {SERVER_VERSION}"
7272
)
7373

7474

mcp-server/tests/unit/test_v0511_coordination.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ class TestFreeToolRegression:
588588

589589
def test_server_version_is_0513(self):
590590
from verifimind_mcp.server import SERVER_VERSION
591-
assert SERVER_VERSION == "0.5.36"
591+
assert SERVER_VERSION == "0.5.37"
592592

593593
def test_wrap_response_still_works(self):
594594
from verifimind_mcp.server import wrap_response

mcp-server/tests/unit/test_v0512_polar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,4 +549,4 @@ def test_no_stripe_in_tier_gate(self):
549549
class TestServerVersion:
550550
def test_server_version_is_0513(self):
551551
from verifimind_mcp.server import SERVER_VERSION
552-
assert SERVER_VERSION == "0.5.36"
552+
assert SERVER_VERSION == "0.5.37"

mcp-server/tests/unit/test_v0516_trinity_history.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,4 @@ def test_run_full_trinity_calls_persist(self):
159159

160160
def test_server_version_is_0516(self):
161161
from verifimind_mcp.server import SERVER_VERSION
162-
assert SERVER_VERSION == "0.5.36", f"Expected 0.5.36, got {SERVER_VERSION}"
162+
assert SERVER_VERSION == "0.5.37", f"Expected 0.5.37, got {SERVER_VERSION}"

0 commit comments

Comments
 (0)