perf: bound publisher profile, membership, and catalog reads
Author: vyctorbrzezowskiCreated Jul 13, 2026Updated Sep 18, 2026
LabelsenhancementP2clawsweeper:source-reproclawsweeper:no-new-fix-prclawsweeper:needs-maintainer-reviewclawsweeper:needs-product-decisionissue-rating: 🦞 diamond lobsterimpact:other
Summary
Several publisher-profile and authenticated publisher paths read or serialize complete resource catalogs even when the caller only needs identity, role, counters, or one page. These paths are currently bounded only at the response layer, not at the database-read layer.
Related umbrella: #2925.
Confirmed source evidence
publishers.getProfileByHandlecallstoPublisherListItemwithforceComputedStats,includePublishedItems, andincludeStarredCount. This can collect all active skills/packages and all stars.src/routes/user/$handle.tsxdoes not consumepublisher.publishedItems, and it invokes the profile query once in the loader and again as a subscription.publishers.listMinecollects the user's memberships, then requests all published items for every publisher. There are eight runtime subscribers; only the destructive summary in Settings consumespublishedItems.publishers.listPublishedPagecollects and sorts every active skill and package before applying its offset cursor withslice.publishers.getPublishedDisplayManifestis a sibling profile path that also collects the complete published catalog for GitHub-backed publishers.
These are source-proven unbounded read shapes. This issue does not claim production p50/p95 or billing numbers because Convex production Insights were unavailable during the audit.
Proposed outcome
- Make the profile summary use complete denormalized counters when available, with the existing computed fallback for legacy incomplete publishers. Do not include
publishedItems. - Add a lightweight membership summary for the eight
listMineconsumers. - Load deletion inventory only while the relevant destructive dialog is open. Keep authorization in the deletion mutation.
- Paginate the published catalog directly through the existing publisher-scoped index for the requested
kind. - Explicitly bound, redesign, or document the remaining
getPublishedDisplayManifestbehavior before claiming the profile path is fully bounded.
Non-goals
- Changing profile reactivity solely for performance.
- Adding a migrated
starredCountcounter in the first change. - Solving alternate sorting of starred skills. That requires workload data and a product/consistency decision.
- Building a mixed skill/plugin cursor: the only runtime catalog caller currently supplies
kind.
Acceptance criteria
- With complete publisher counters, profile and membership summary tests fail if
skillsorpackagesare queried. - Missing-counter fixtures preserve the existing computed fallback and visibility rules.
- The membership summary contains no
publishedItems; deletion inventory is skipped until its dialog opens. - Published skill/plugin pages use indexed pagination without
.collect()and have no duplicate or missing rows across cursors. - Soft-deleted, blocked, official, legacy-personal, deleted-user, and deactivated-publisher cases retain current behavior.
- The destructive mutation's authorization and deletion semantics remain unchanged.
Validation
- Focused Convex tests that enforce table-read bounds, not only returned shape.
- Profile and Settings component tests for query
skipbehavior. - Seeded large-publisher A/B for documents read and serialized bytes when deployment Insights are available.
Source: openclaw/clawhub