Skip to content

feat(subnets): serve the per-subnet event summary from the lakehouse - #9304

Merged
JSONbored merged 1 commit into
mainfrom
feat/subnet-event-summary-cold-tier
Aug 3, 2026
Merged

feat(subnets): serve the per-subnet event summary from the lakehouse#9304
JSONbored merged 1 commit into
mainfrom
feat/subnet-event-summary-cold-tier

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

/api/v1/subnets/{netuid}/event-summary reported total_events: 0 for every netuid, while
/subnets/{netuid}/events served real rows off the same account_events stream:

netuid /event-summary /events
1, 8, 19, 64 0 rows

Two views of one stream, opposite answers — the same shape as #9260. All three surfaces ran
tryPostgresTier(…) ?? buildSubnetEventSummary([], [], …) and nothing Cloudflare-native replaced
the deleted Postgres tier.

The obvious port is rejected by the engine

One grouped rollup with count(*) plus count(DISTINCT hotkey) and count(DISTINCT coldkey) fails
at this route's own default window:

40015: scan budget exceeded: scanning too much data for count(DISTINCT),
count(DISTINCT) with GROUP BY

Note "with GROUP BY". Unlike #9252, #9261 and #9280 — where the fix was adding a GROUP BY
adding one is not the fix here. Two distincts in a single grouped scan exceed the budget on their
own, and this route also offers 90d, three times the span that already fails.

Each distinct is therefore distributed into its own nested aggregation: group to the
(kind, key) pairs, then count the pairs per kind — the form the engine's own error message asks
for.

hotkey IS NOT NULL is load-bearing, not tidiness. COUNT(DISTINCT col) ignores NULLs while
GROUP BY col yields a NULL group, so without it every kind whose rows carry no hotkey
(WeightsSet, and every Balances kind for coldkey) would report one participant that does not
exist
.

An empty window is a measured zero

The query layer returns null on failure and [] on a successful empty scan, so a quiet subnet
publishes a real zero rather than being indistinguishable from the broken tier this fixes. A failed
read still declines, so the card can never pair real counts with zeroed participants.

Verification

Live against the lakehouse, netuid 64 over 30d: WeightsSet 9,832 events; StakeAdded
8,517 events across 66 distinct hotkeys and 2,109 distinct coldkeys. All four query
shapes confirmed to run — including the rejected one, to prove the rejection.

Six mutations; five caught:

mutation tests failed
collapse to one grouped count(DISTINCT) 6
drop IS NOT NULL from the distinct reads 1
drop the failed-read guard 1
accept a zero limit 1
let an unknown window fall back to 30d 1
key a malformed distinct row as String(kind) 0 — equivalent mutant

The last is reported honestly: entries for kinds absent from the base rollup are never looked up, so
that guard has no observable effect. Its branch is covered; its mutation is not detectable, and I
did not add a test that would only appear to detect it.

Writing the limit test found a real defect in my first draft?? only catches
null/undefined, so a literal 0 sailed through the default resolution and produced LIMIT 0, a
silently empty recent-events page. parseLimitParam rejects 0 at the REST edge, but MCP and
GraphQL call this reader directly.

I also removed three guards that could never fire rather than leave untestable defensive code: a
cap floor the resolution already guarantees, an arithmetic check on a cutoff derived from an
already-validated window, and a column-name guard over two string literals.

Patch coverage 21/21 = 100% with every branch taken, measured by intersecting the diff's line
ranges with v8's uncovered set. 1,513 tests pass across the affected suites; tsc --noEmit,
prettier and eslint clean.

One pre-existing failure is unrelated and reproduces on clean origin/main:
mcp-server.test.ts > "resolves real artifacts from the local env" (service_count >= 1) needs
locally built artifacts, which CI produces.

Closes #9303

/api/v1/subnets/{netuid}/event-summary reported total_events 0 for EVERY
netuid -- 1, 8, 19 and 64 all answered zero -- while /subnets/{netuid}/events
served real rows off the same account_events stream. Two views of one stream,
opposite answers, the same shape as #9260.

All three surfaces ran tryPostgresTier(METAGRAPH_ACCOUNT_EVENTS_SOURCE) ??
buildSubnetEventSummary([], [], ...) and nothing Cloudflare-native replaced the
deleted Postgres tier, so add one shared reader and wire all three at once.

THE OBVIOUS PORT IS REJECTED. One grouped rollup carrying count(*) plus
count(DISTINCT hotkey) and count(DISTINCT coldkey) fails at this route's own
default window:

  40015: scan budget exceeded: scanning too much data for count(DISTINCT),
  count(DISTINCT) with GROUP BY

Note "with GROUP BY". Unlike #9252, #9261 and #9280 -- where the fix was ADDING
a GROUP BY -- adding one is not the fix here: two distincts in a single grouped
scan exceed the budget on their own, and this route also offers 90d, three
times the span that already fails. So each distinct is distributed into its own
nested aggregation, grouping to the (kind, key) pairs and then counting the
pairs per kind.

`hotkey IS NOT NULL` is load-bearing rather than tidy. COUNT(DISTINCT col)
ignores NULLs while GROUP BY col yields a NULL group, so without it every kind
whose rows carry no hotkey -- WeightsSet, and every Balances kind for coldkey --
would report one participant that does not exist.

An empty window is a MEASURED ZERO, not a decline: the query layer returns null
on failure and [] on a successful empty scan, so a quiet subnet can say so
rather than being indistinguishable from the broken tier this fixes. A failed
read still declines, so the card can never pair real counts with zeroed
participants.

Verified live for netuid 64 over 30d: WeightsSet 9,832 events, StakeAdded 8,517
events across 66 distinct hotkeys and 2,109 distinct coldkeys.

Writing the limit test found a real defect in the first draft: `??` only
catches null/undefined, so a literal 0 sailed through the default resolution
and produced LIMIT 0 -- a silently empty recent-events page. parseLimitParam
rejects 0 at the REST edge, but MCP and GraphQL call this reader directly. The
positivity check is now part of resolving an unusable limit rather than a
separate guard.

Three guards that could never fire were removed rather than left as untestable
defensive code: a cap floor the resolution already guarantees, an arithmetic
check on a cutoff derived from a validated window, and a column-name guard over
two string literals.

Closes #9303
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-data-api 8621620 Aug 03 2026, 08:29 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
metagraphed-registry-sync-api 8621620 Aug 03 2026, 08:29 PM

@superagent-security

Copy link
Copy Markdown

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored
JSONbored merged commit 14086d3 into main Aug 3, 2026
7 checks passed
@JSONbored
JSONbored deleted the feat/subnet-event-summary-cold-tier branch August 3, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subnets/{netuid}/event-summary reports zero for every subnet, and the obvious port is rejected by the scan budget

1 participant