Skip to content

feat(mcp): add chain block-explorer and account tail tools (#2033) #4763

feat(mcp): add chain block-explorer and account tail tools (#2033)

feat(mcp): add chain block-explorer and account tail tools (#2033) #4763

Workflow file for this run

name: Validate
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
concurrency:
group: validate-${{ github.ref }}
cancel-in-progress: true
# Every contributor PR runs the FULL validation — there is NO reduced "UGC"
# fast-lane. The old lane skipped the safety scans for "single community file"
# PRs and kept causing a stale-base preflight false-positive; community surfaces
# now live in one file per subnet (registry/subnets/<slug>.json) and are checked
# by the same gates as everything else (validate:surface + public-safety +
# private-boundary, all below).
#
# The work splits across two parallel jobs that both build: `test` builds then
# runs the suite + coverage; `checks` builds then runs lint + the ~20 contract /
# schema / safety validations. They are independent — no validation needs the
# test results — so two runners make the wall-clock max(test, checks), not the
# sum. Both build because the suite's reader tests serve R2-only artifacts
# (registry-summary.json etc.) that have NO committed fallback and only exist
# after `npm run build`, so a fresh runner must stage them.
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22.23.0
cache: npm
- name: Install
run: npm ci
# Stage artifacts before the suite: the reader tests serve R2-only
# artifacts that have NO committed fallback (dist/metagraph-r2/metagraph/*,
# e.g. registry-summary.json), which only exist after a build.
- name: Build artifacts
env:
METAGRAPH_PRESERVE_PROBE_HEALTH: "1"
run: npm run build
- name: Prepare test reports dir
run: mkdir -p reports/junit
# The suite runs in two non-overlapping passes (see vitest.config.mjs's
# fileParallelism note). `test:ci` runs the parallelizable bulk — every
# file EXCEPT the two filesystem-mutating artifact writers — concurrently
# WITH coverage; `test:ci:artifacts` (the last step below) then runs those
# two writers serially, after coverage is already captured + uploaded. The
# writers drive their work via execFileSync child processes and add zero
# in-process coverage, so coverage comes only from `test:ci` and CI still
# makes a single Codecov upload.
- name: Test (parallel, with coverage gate)
env:
VITEST_JUNIT_PATH: reports/junit/vitest.xml
run: npm run test:ci
# Upload coverage to Codecov — the primary PR coverage gate (delta-based
# project + patch, see codecov.yml). vitest's thresholds are now just a
# local backstop. Runs on PRs AND main pushes (main = the comparison base);
# the upload failing never fails CI (the gate is Codecov's own status).
- name: Upload coverage to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
fail_ci_if_error: false
- name: Upload Vitest results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./reports/junit/vitest.xml
report_type: test_results
fail_ci_if_error: false
# The two filesystem-mutating artifact writers, run serially LAST so they
# never overlap the parallel readers above and coverage is already
# uploaded. They mutate the same on-disk artifact tree, so they cannot run
# concurrently with each other either — the serial default applies. This is
# a pure correctness gate (no coverage); a failure here still fails the job.
- name: Test (artifact writers, serial)
run: npm run test:ci:artifacts
checks:
name: checks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
# Compute the deletion-filtered list of files this PR submits, inline in the
# trusted workflow (PR contents are untrusted). It feeds ONLY the
# reproducible-artifact verifier below, which diff-scopes its check to the
# files actually changed. There is no reduced lane to forge — every PR runs
# the full validation regardless of this list.
- name: Compute submitted files
shell: bash
env:
BASE_REF: ${{ github.base_ref || 'main' }}
BASE_SHA: ${{ github.event.pull_request.base.sha || '' }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
if [ -n "$BASE_SHA" ] && [ -n "$HEAD_SHA" ]; then
git diff --name-only --diff-filter=d "$BASE_SHA"..."$HEAD_SHA" > submitted-artifact-files.txt
else
git fetch origin "$BASE_REF"
git diff --name-only --diff-filter=d "origin/$BASE_REF"...HEAD > submitted-artifact-files.txt
fi
else
: > submitted-artifact-files.txt
fi
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 22.23.0
cache: npm
- name: Install
run: npm ci
- name: Lint + format
run: |
npm run lint
npm run format:check
# Drift gate: these compare the COMMITTED contract (openapi.json, the schema
# bundle, generated types + client) against what the schemas regenerate, so
# they MUST run before the build step below overwrites those committed files.
# This is the gate CONTRIBUTING.md references — without it a stale or
# hand-edited committed contract could land on main undetected.
- name: Validate committed contract (drift)
run: |
npm run validate:contract-drift
npm run validate:schema-enums
npm run validate:openapi-examples
npm run validate:generated-client
npm run validate:committed-seed
- name: Build artifacts
env:
METAGRAPH_PRESERVE_PROBE_HEALTH: "1"
run: npm run build
- name: Verify submitted public artifacts are reproducible
if: github.event_name == 'pull_request'
run: node scripts/ci-verify-submitted-artifacts.mjs --changed-files submitted-artifact-files.txt
- name: Validate registry
run: npm run validate
- name: Generate R2 manifest
run: npm run r2:manifest
# #1025: committed derived artifacts under public/ must match a fresh
# build. The validators above run AFTER `npm run build`, so they only ever
# see freshly-rebuilt files — never the committed-stale ones — and
# ci-verify is diff-scoped, so a source-only PR never re-checks untouched
# derived files. This gate closes that silent-drift hole: a full rebuild
# has just run (build + r2:manifest), so any working-tree change here means
# a committed artifact was left stale. The deterministic derived artifacts
# use a fixed reproducible `generated_at` placeholder; the R2-only dist/
# tree is gitignored and excluded automatically.
#
# r2-manifest.json is excluded: its `*_artifact_size_bytes` totals sum the
# R2-only artifacts, whose byte sizes vary slightly build-to-build, so it
# is inherently non-deterministic. It is publish infrastructure regenerated
# on every publish (and by the r2:manifest step above), so its committed
# copy is a best-effort lockfile, not a served/contract surface.
- name: Verify committed derived artifacts are fresh
run: |
# Only CONTRACT artifacts are gated here — openapi.json / types.d.ts /
# contracts.json / api-index.json — because they change only with reviewed
# schema/source PRs and MUST be regenerated + committed. DATA/CONTENT-derived
# artifacts are NOT gated: they re-drift on every registry/data edit and would
# red-line build-less community surface PRs (the single-file model makes adding
# a surface a one-file data PR). Those are handled out-of-band: public/datasets/
# + the llms.txt catalogs are gitignored (rebuilt + served via ASSETS on
# deploy); r2-manifest.json is the publish lockfile; schemas/index.json is a
# network-capture cache the build reconciles in place (a new openapi surface
# legitimately adds a not-captured placeholder); and operational-surfaces.json
# is CONTENT-derived — the prober's input list regenerates whenever a
# probe-enabled, public-safe operational-kind surface (subnet-api / sse /
# data-artifact / wss / rpc) is added or changed, which a one-file community
# surface PR does NOT regenerate (there is no authority/review gate on that
# list). It stays committed so the prober's ASSETS read succeeds on a cold
# start, but the served copy is rebuilt fresh on every deploy — so a
# momentarily-stale git copy is a tolerated cache, not a runtime fault.
excludes=(
':(exclude)public/metagraph/r2-manifest.json'
':(exclude)public/metagraph/schemas/index.json'
':(exclude)public/metagraph/operational-surfaces.json'
':(exclude)public/datasets'
)
status=$(git status --porcelain --untracked-files=all -- public/ "${excludes[@]}")
if [ -n "$status" ]; then
echo "::error::Committed derived artifacts under public/ are stale (a fresh build changed, deleted, or recreated them). Run 'npm run build' and commit the result."
printf '%s\n' "$status"
git diff --name-only -- public/ "${excludes[@]}"
git diff --stat -- public/ "${excludes[@]}"
exit 1
fi
echo "All committed derived artifacts under public/ match a fresh build."
- name: Validate JSON schemas
run: npm run validate:schemas
- name: Validate Worker API
run: npm run validate:api
- name: Validate MCP server
run: npm run validate:mcp
- name: Validate AI routes
run: npm run validate:ai
- name: Validate OpenAPI
run: npm run validate:openapi
- name: Validate generated API types
run: npm run validate:types
# Client-SDK drift gate: a contract-only PR (schemas → generated →
# openapi.json) is NOT under packages/client/**, so release-please never
# bumps the `client` component and the published @jsonbored/metagraphed
# silently lags the live contract. This fails the PR when a contract file
# changed versus the merge base but packages/client/package.json "version"
# was not bumped. Diff-scoped: non-contract PRs never fire.
- name: Validate client-SDK sync (contract ↔ published version)
if: github.event_name == 'pull_request'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: npm run validate:client-sdk-sync
- name: Validate artifact size budgets
run: npm run validate:artifact-budgets
- name: Validate documentation contracts
run: npm run validate:docs
# NOTE: the README subnet catalog is intentionally NOT gated here. It is a
# DATA-derived listing (regenerated by `npm run readme:catalog` from
# registry/subnets/), so gating it red-lined build-less community surface
# PRs that legitimately change a subnet's catalog row. README freshness is
# owned out-of-band by .github/workflows/readme-catalog-refresh.yml, which
# accumulates drift into a single chore/readme-catalog PR on every
# registry/subnets push. (Same rationale as the gitignored llms.txt
# catalogs + public/datasets — DATA artifacts are not PR-gated.)
- name: Validate intake templates
run: npm run validate:intake
- name: Validate registry subnet files
run: npm run validate:surface
- name: Validate workflows
run: npm run validate:workflows
- name: Validate migration sequence
run: npm run validate:migrations
- name: Validate Cloudflare config
run: npm run cloudflare:verify:dry-run
- name: Validate R2 manifest
run: |
npm run r2:manifest:dry-run
npm run r2:download:dry-run
npm run kv:publish:dry-run
- name: Worker deploy dry-run
run: npm run worker:deploy:dry-run
- name: Worker bundle-size budget
run: npm run worker:bundle:budget
- name: Public-safety scan
run: npm run scan:public-safety
- name: Validate private boundary
run: npm run validate:private-boundary