Skip to content

Key-fingerprint-based analytics identity - #440

Merged
akolotov merged 11 commits into
mainfrom
claude/issue-437-impl-plan-0a53c8
Jul 29, 2026
Merged

Key-fingerprint-based analytics identity#440
akolotov merged 11 commits into
mainfrom
claude/issue-437-impl-plan-0a53c8

Conversation

@akolotov

@akolotov akolotov commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Makes the Mixpanel distinct_id derive from the PRO API key fingerprint instead of the IP/client-name/client-version composite, so a user keeps a stable analytics identity across client upgrades, IP churn, and transport changes.

Closes #437.

What changed

  • New helper _build_fingerprint_distinct_id(fingerprint) in blockscout_mcp_server/analytics.pyuuid5(NAMESPACE_URL, "https://mcp.blockscout.com/mcp" + "key:" + fingerprint). The key: domain tag keeps the two bases' input spaces from ever colliding.
  • Two identity bases, keyed by path:
    • Direct path (track_tool_invocation / track_resource_read): fingerprint basis only when auth_origin == "client" — a server-key fingerprint there identifies the deployment, not a user.
    • Community path (track_community_usage): fingerprint basis whenever one is present, regardless of origin — for a self-hosted report, a server-key fingerprint identifies the installation and a client-key fingerprint identifies that installation's individual users, so either is meaningful.
    • Both fall back to the legacy composite when no fingerprint is available, so legacy reporters keep being counted.
  • Callers threaded: tools/decorators.py and observability.py now pass api_key_fingerprint through to analytics.
  • Privacy invariant preserved: the fingerprint is used only to derive the distinct_id and is never added to the event properties.
  • Docstring alignment across telemetry.py, models.py, pro_api_key_context.py; SPEC.md and AGENTS.md updated; version bumped to 0.18.0.dev0.

Testing

  • Unit suite: 987 passed, 0 skipped, 0 xfailed. New tests/test_analytics_identity.py covers the full basis-selection matrix for both paths, plus the relocated fingerprint-leak test.
  • Coverage on the changed lines in analytics.py / tools/decorators.py / observability.py confirmed exercised.
  • Integration suite via the timeout-protected runner: 92 passed, 0 failed, 0 skipped, 0 timed out.
  • HTTP-mode end-to-end against a local self-signed HTTPS capture endpoint: the same client PRO key sent through a direct REST tool call, a /skill/SKILL.md resource read, and POST /v1/report_tool_usage produced 4 events all carrying the same fingerprint-derived distinct_id, independently recomputed from the raw key. Zero occurrences of the raw key and zero 64-hex digests anywhere in the captured payloads.
  • ruff check . and ruff format --check . clean across 215 files.

Reviewer notes

  • The asymmetry between the direct and community paths is deliberate, not an oversight — see the track_community_usage docstring for the reasoning.
  • Existing distinct_ids change for authenticated users. Historical events keyed on the old composite will not merge with the new identity.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved analytics identity handling with deterministic UUIDv5 distinct_id derived from API key fingerprints when applicable.
    • Extended tool invocation and resource-read analytics to accept and use a pre-computed API key fingerprint for identity basis selection.
    • Added expanded test coverage for direct vs community identity-basis selection and ensured the fingerprint is not exposed in Mixpanel payloads.
  • Bug Fixes

    • Ensured malformed or absent fingerprints degrade cleanly to the existing heuristic identity behavior.
  • Documentation

    • Updated spec and guidance for anonymous identity and community telemetry authorization-context behavior.
  • Chores

    • Bumped version to 0.18.0.dev0.
    • Improved test helpers and observability wiring assertions for analytics fan-out.

akolotov and others added 7 commits July 28, 2026 20:08
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b1fab4ee-71f7-4106-a69b-39e7864280df

📥 Commits

Reviewing files that changed from the base of the PR and between e958b07 and c59ff5f.

📒 Files selected for processing (1)
  • SPEC.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • SPEC.md

Walkthrough

Analytics now uses validated API-key fingerprints for selected Mixpanel identities, preserving heuristic fallbacks. Fingerprints flow through direct, resource-read, and community telemetry paths, with updated tests, documentation, and version metadata.

Changes

Analytics identity propagation

Layer / File(s) Summary
Identity selection and contracts
blockscout_mcp_server/analytics.py, blockscout_mcp_server/models.py, SPEC.md, AGENTS.md
Adds deterministic fingerprint-based distinct_id generation, path-specific selection rules, and heuristic fallback behavior for missing or malformed fingerprints.
Telemetry and analytics signal wiring
blockscout_mcp_server/tools/decorators.py, blockscout_mcp_server/observability.py, blockscout_mcp_server/{telemetry,pro_api_key_context}.py
Threads api_key_fingerprint from derived authorization signals into tool invocation and resource-read analytics.
Identity and propagation tests
tests/test_analytics_identity.py, tests/test_analytics_helpers.py, tests/tools/test_decorators.py, tests/test_observability.py, tests/conftest.py, tests/analytics_ctx_helpers.py, tests/api/test_routes.py, tests/test_analytics.py, tests/test_tool_usage_report.py
Covers UUID generation, basis selection, privacy, fallback behavior, forwarding, shared test doubles, and analytics-state isolation.
Release metadata
pyproject.toml, server.json, blockscout_mcp_server/__init__.py
Updates the project, manifest, and package versions to 0.18.0.dev0.

Sequence Diagram(s)

sequenceDiagram
  participant ToolDecorator
  participant Analytics
  participant Mixpanel
  ToolDecorator->>Analytics: pass auth_origin and api_key_fingerprint
  Analytics->>Analytics: choose fingerprint or heuristic distinct_id
  Analytics->>Mixpanel: send event with derived distinct_id
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The version bumps in init.py, pyproject.toml, and server.json are unrelated to #437's analytics identity change. Move version-only updates to a separate release PR, or document them as intentional release housekeeping if they must stay bundled.
Docstring Coverage ⚠️ Warning Docstring coverage is 62.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: analytics identity now uses key fingerprints.
Linked Issues check ✅ Passed The changes adopt the PRO key fingerprint where meaningful, retain legacy fallback, and avoid exposing the fingerprint in analytics payloads.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-437-impl-plan-0a53c8

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@akolotov akolotov self-assigned this Jul 29, 2026
@akolotov akolotov changed the title Key-fingerprint-based analytics identity (#437) Key-fingerprint-based analytics identity Jul 29, 2026
akolotov and others added 3 commits July 28, 2026 21:25
- Correct the disjointness rationale in _build_fingerprint_distinct_id:
  the "|" separators, not the "key:" tag, are the structural guarantee
  (a composite CAN start with "key:" via a spoofed x-forwarded-for);
  pin that adversarial case in the domain-separation test.
- Pin the documented defensive row on the direct path: unknown/"none"
  origin with a fingerprint present still selects the legacy basis.
- SPEC.md: record the accepted analytics-integrity risk that a forged
  community report can direct events into any chosen fingerprint-derived
  identity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Collapse the three community-path fingerprint-basis tests into one
  parametrized test over auth_origin (client/server/None), matching the
  parametrize idiom already used for the direct-path rows.
- Move the duplicated reset_mode_and_client fixture into tests/conftest.py
  as a shared, opt-in reset_analytics_state fixture (module-wide via
  pytestmark); drop the post-yield _mp_client reset, which monkeypatch
  teardown made a no-op.
- Trim the UUIDv5 derivation detail from the api_key_fingerprint wire-schema
  description in models.py — the recipe stays documented in SPEC.md; the
  public description keeps only what a reporting instance needs to know.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Extract the duplicated DummyRequest/DummyCtx test doubles from
  tests/test_analytics.py and tests/test_analytics_identity.py into a shared
  tests/analytics_ctx_helpers.py module (same pattern as
  pro_api_key_helpers.py) and list it in the AGENTS.md tree.
- Widen the test_analytics_identity.py module docstring: the module pins the
  basis-selection matrix for the direct path and the PageView carve-out, not
  only the community-reporting path.
- Pin the community-path degenerate row auth_origin="none" with a present
  fingerprint: well-behaved reporters never send it, but the wire accepts it
  and the "any present fingerprint" rule must hold there too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@akolotov
akolotov marked this pull request as ready for review July 29, 2026 04:02
@akolotov

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: e958b0773f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
AGENTS.md (1)

367-367: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep implementation mechanics out of AGENTS.md.

The distinct_id derivation algorithm is implementation-specific module documentation. Keep this entry at a high level and link readers to SPEC.md or analytics.py for the identity rules.

As per coding guidelines, AGENTS.md should remain a search hint rather than implementation-specific module documentation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 367, Update the AGENTS.md entry describing anonymous
distinct_id generation to a high-level search hint, removing the key-fingerprint
and IP/name/version algorithm details. Direct readers to SPEC.md or analytics.py
for the identity rules while preserving the entry’s purpose and scope.

Source: Coding guidelines

SPEC.md (1)

946-946: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep wire-contract details out of SPEC.md.

This paragraph mixes architectural rationale with validator/coercion and wire-format behavior. Keep the accepted analytics-integrity risk and pepper decision here, but defer exact contract details to API.md and the implementation.

As per coding guidelines, SPEC.md should focus on architecture and decisions rather than exhaustive technical behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SPEC.md` at line 946, Condense the “Authorization context” section in SPEC.md
to retain only the architectural rationale, accepted analytics-integrity risk,
and server-side HMAC pepper decision. Remove exact field names, enum/coercion
behavior, fingerprint wire-format constraints, and validator-specific details,
directing contract specifics to API.md and implementation validators.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SPEC.md`:
- Around line 929-930: Revise the distinct_id collision rationale in SPEC.md to
describe the actual input-shape separation: key-derived inputs are “key:”
followed by a 64-character hexadecimal digest, while legacy composite inputs
contain “|”. Remove the claim that the key: prefix alone prevents collisions,
and state the guarantee based on these distinct shapes.

---

Nitpick comments:
In `@AGENTS.md`:
- Line 367: Update the AGENTS.md entry describing anonymous distinct_id
generation to a high-level search hint, removing the key-fingerprint and
IP/name/version algorithm details. Direct readers to SPEC.md or analytics.py for
the identity rules while preserving the entry’s purpose and scope.

In `@SPEC.md`:
- Line 946: Condense the “Authorization context” section in SPEC.md to retain
only the architectural rationale, accepted analytics-integrity risk, and
server-side HMAC pepper decision. Remove exact field names, enum/coercion
behavior, fingerprint wire-format constraints, and validator-specific details,
directing contract specifics to API.md and implementation validators.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bc00615-76ae-4de8-91a9-c27b2f88663c

📥 Commits

Reviewing files that changed from the base of the PR and between a5f2c3f and e958b07.

📒 Files selected for processing (20)
  • AGENTS.md
  • SPEC.md
  • blockscout_mcp_server/__init__.py
  • blockscout_mcp_server/analytics.py
  • blockscout_mcp_server/models.py
  • blockscout_mcp_server/observability.py
  • blockscout_mcp_server/pro_api_key_context.py
  • blockscout_mcp_server/telemetry.py
  • blockscout_mcp_server/tools/decorators.py
  • pyproject.toml
  • server.json
  • tests/analytics_ctx_helpers.py
  • tests/api/test_routes.py
  • tests/conftest.py
  • tests/test_analytics.py
  • tests/test_analytics_helpers.py
  • tests/test_analytics_identity.py
  • tests/test_observability.py
  • tests/test_tool_usage_report.py
  • tests/tools/test_decorators.py

Comment thread SPEC.md Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@akolotov
akolotov merged commit 72e765e into main Jul 29, 2026
8 checks passed
@akolotov
akolotov deleted the claude/issue-437-impl-plan-0a53c8 branch July 29, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use the PRO API key fingerprint as the analytics identity where it is meaningful

1 participant