feat(MMQA-1951): add new action to download segment analytics contracts - #296
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb9dc03278
ℹ️ 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".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bb9dc03. Configure here.
…etaMask#36941) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> <!-- mms-check directive vocabulary — read by .github/scripts/shared/pr-template-checks.ts at module load to build the validation plan. Directives are invisible in rendered markdown and must NOT be removed or edited without updating the validator registry. type=text Section must contain non-placeholder prose. type=changelog Section must have a valid CHANGELOG entry: line. type=issue-link Section must have a Fixes:/Closes:/Refs: line with a value. type=manual-testing Section must have real testing steps or an explicit N/A. type=screenshot Section must have evidence (image/URL) or an explicit N/A. type=checklist Section must have all checkboxes consciously checked. required=true|false Whether a missing/invalid section runs the validator at all. blocking=true|false Whether a failure of this check fails the CI workflow. Default: false — failures are shown as warnings in the sticky comment but do not block the PR. Sections without a directive are checked for structural presence only. --> ## **Description** This PR introduces the first Mobile-side consumer flow for deployed typed analytics contracts, focused on CI validation and reproducibility. It downloads the immutable private Mobile contract through the shared OIDC action, verifies its metadata and hashes, regenerates the public `Quick Buy v1/v2 types`, and detects drift in CI. **Overall changes** - Download and consumes the deployed Mobile contract through a shared GitHub Action (github-tools) - Verifies release metadata, asset digest, semantic contract hash, source SHA, tracking-plan ID, and rule count. - Adds a pinned contract lock for reproducible local validation. - Publishes strict Quick Buy Amount Selected v1/v2 TypeScript types. - Validates that tracked public types match regenerated output in CI. - Preserves Mobile’s existing analytics runtime and dispatch behavior. - Adds runtime tests for event-name and version-context forwarding. - No production analytics call-site migration. - No changes to `@metamask/analytics-controller`. - No automatic contract-update PR yet. - No progressive runtime builder yet. ## Public generated types The generated Quick Buy facade is tracked publicly at: - `app/util/analytics/generated/QuickBuyAmountSelected.ts` - `app/util/analytics/generated/QuickBuyAmountSelected.test-d.ts` It provides: - explicit `v1` and `v2` APIs; - required-property checks; - enum/value checks; - unknown-property rejection; - `context.protocols.event_version`. The raw contract remains private, i.e. the `metamask-mobile.json` contract downloaded from segment-schema release is never committed. ## Generator comparison We compared two approaches against the same immutable Mobile contract and the same `Quick Buy Amount Selected` v1/v2 fixture. - Release: `analytics-contracts-67390efdfde3-8db9e9f80441` - Contract hash: `sha256:f75e241c1a832b22c80f9537765e51dac0ba380133f1ec90a6f3e7c77235c4c3` - Fixture: `Quick Buy Amount Selected` v1/v2 ### Lightweight custom generator The custom generator consumes the verified deployed Mobile contract and a reviewed caller-property boundary. It generates: - separate v1 and v2 property interfaces; - required and optional property checks; - enum/string-literal validation; - unknown-property rejection; - explicit `context.protocols.event_version`; - compile-time fixtures for invalid calls; - a runtime wrapper test for event name and version forwarding. ### Stock Typewriter The Typewriter comparison used TypeScript with `analytics-react-native` and a temporary local `plan.json` derived from the same pinned contract. Its output was used only as reference material. **Typewriter was included only for evaluation and should have been removed before the final merge; it is not a production dependency or the selected implementation.** Stock Typewriter: - retains only the highest version for a repeated event key, so the fixture produces v2 but loses the explicit v1 API; - emits a permissive `[property: string]: any` index signature; - does not emit the required event-version context; - does not provide the progressive builder API; - does not provide the exact caller-property boundary needed by this pilot. ### Result The lightweight generator is the better fit for this Mobile pilot because it preserves event versions, enforces exact properties, and supplies the Segment event-version context without adding a runtime dependency or requiring Segment credentials. This is not yet a final decision for the all-events generator. Typewriter’s schema normalization and generation code may still provide reusable ideas. The Typewriter dependency and comparison files are temporary spike material and will be removed after review. The temporary evaluation files are available in the initial spike commit: [comparison spike](MetaMask@f956d52) The Typewriter comparison was used as a temporary design spike and has been removed from the final implementation. The comparison showed that the narrow generator is a better fit for this versioned Mobile pilot because it preserves v1/v2, rejects unknown properties, and adds event-version context. ## Security and privacy - The raw contract is never committed. - GitHub Actions uses short-lived OIDC access through the token exchange service. - Local contract and generated comparison output are written only under ignored `temp/`. - The committed lock contains release identifiers and hashes only. ## Validation Passed: - `yarn analytics:contract:check:locked` - `yarn analytics:contract:compare` - `yarn jest scripts/analytics/typed-events --runInBand --coverage=false --forceExit` - targeted TypeScript check - targeted ESLint - Prettier check - regenerated public facade matches tracked files exactly Targeted checks passed. ## Follow-up 1. Remove the temporary Typewriter spike files in this PR before merge. DONE. ✅ 2. **After merge**, manually dispatch the workflow from `main`. 3. Confirm OIDC access, contract verification, public-type drift checks, and Slack notification. 4. Open and merge a follow-up PR integrating the generated facade into one production Quick Buy call. 5. After step 4 is merged, add the trusted rolling-update workflow. It should: - detect a newer deployed contract release; - update `contract.lock.json`; - regenerate the public Quick Buy types; - run tests and drift checks; - open a reviewable bot PR. 6. Add progressive event construction and runtime completeness validation. 7. Expand typed coverage to additional event families. <!-- mms-check: type=text required=true --> <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- mms-check: type=changelog required=true blocking=true --> <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** - Refs: https://consensyssoftware.atlassian.net/browse/MMQA-1951 - Refs: Consensys/segment-schema#583 - Refs: MetaMask/github-tools#296 <!-- mms-check: type=issue-link required=true --> Fixes: ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> In description ## **Screenshots/Recordings** <!-- mms-check: type=screenshot required=true --> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> - From local, proving the schema contract is downloaded and verified: <img width="994" height="385" alt="Screenshot 2026-09-29 at 15 42 37" src="https://github.com/user-attachments/assets/bd8f5eb0-847a-4291-a244-47b549615f13" /> - Temporary files dowloaded and generated: <img width="294" height="162" alt="Screenshot 2026-09-29 at 15 45 19" src="https://github.com/user-attachments/assets/20bc7439-10e3-419f-937b-f089e4d0c72f" /> - The generated public facade exposes separate v1 and v2 property contracts, including version-specific fields and the required event-version context (what we have committed in this PR as POC app/util/analytics/generated/QuickBuyAmountSelected.ts): <img width="886" height="493" alt="Screenshot 2026-09-29 at 15 53 18" src="https://github.com/user-attachments/assets/20463bb0-895c-4fad-a6c2-15ad7ada1be6" /> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > CI/tooling and generated types only; no production analytics path or runtime behavior changes. > > **Overview** > Adds **typed analytics contract** validation: Mobile can download the immutable Segment schema release (`metamask-mobile.json`), verify hashes and metadata, regenerate a strict TypeScript facade, and fail when tracked types drift. > > New **`yarn analytics:contract:check`** / **`check:locked`** (plus `scripts/analytics/typed-events/*`) handle release resolution, `contract.lock.json`, semantic hash checks, and a narrow generator for **Quick Buy Amount Selected** v1/v2 with exact properties and `context.protocols.event_version`. Committed pilot output lives under `app/util/analytics/generated/` with runtime and compile-time tests. > > A scheduled **`check-analytics-contract`** workflow uses the shared download action, runs the check, **`diff`s** regenerated files against the repo, writes a job summary, and posts **Slack** on failure. Docs describe the lifecycle; **production analytics dispatch is unchanged** (no call-site migration yet). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0b54ef4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Add reusable action to download analytics contract releases:
Note
Low Risk
New CI plumbing only; no runtime app or auth logic changes, though callers must grant
id-token: writeand trust the token exchange flow.Overview
Adds a reusable composite GitHub Action
download-analytics-contractso workflows can pull Segment analytics contract artifacts from a private repo using OIDC (viaMetaMask/github-tools/get-token) instead of long-lived secrets.Callers pass token exchange URL, target repo, asset name, and output directory; an optional
release-tagpins a version, otherwise the action picks the latest publishedanalytics-contracts-*release (excluding drafts/prereleases). It resolves release metadata, requires a validsha256:digest on the asset, downloadsmanifest.jsonand the named platform file, verifies the on-disk hash, and exposes tag, URL, digest, and absolute paths as outputs.Reviewed by Cursor Bugbot for commit d5b478a. Bugbot is set up for automated code reviews on this repo. Configure here.