Wish bridge: gate auto-issues on qualified demand, not raw count#437
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The wish-to-issue bridge opened a GitHub issue for any cluster reaching count 5. That gate is trivially gamed: a single script POSTing the same string 5 times mints an issue. Observed 2026-07-17: one source drove three clusters to 18-103 identical hits in a few hours (synthora mesh / em spectrum intelligence), each of which auto-opened a junk issue. New clusterQualifies predicate (src/wish.js) requires, on top of the count threshold, a sign of independent demand: either two or more distinct sources (api / mcp / find-miss) or demand sustained past QUALIFY_MIN_SPAN_MS (24h). A genuine gap is hit by different agents across surfaces or recurs over days; a scripted burst is one source in one sitting and clears neither bar. getWishesAggregate exposes a per-cluster qualified flag, and the wish-issues workflow now selects on that flag instead of raw count. A cached pre-deploy response lacks the field, so the gate fails closed and opens nothing. Honest limit (same framing as the router's per-seller Sybil cap): a patient spammer can still drip over 24h or add a decoy hit on a second surface. This raises the cost from 5 curls to sustained-or-multi-surface; it is not full Sybil resistance. The wish is always recorded and visible on /api/wishes regardless; only auto-issue creation is gated. Tests: clusterQualifies unit cases (below-threshold, single-source burst, both synthora shapes, two-source, span boundary) plus end-to-end through recordWish + getWishesAggregate. Aggregate-shape and demand-radar consumer tests updated for the additive field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MikeyPetrillo
marked this pull request as ready for review
July 18, 2026 01:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The wish-to-issue bridge opened a GitHub issue for any wish cluster reaching count 5, with no quality check. That gate is trivially gamed: a single script POSTing the same string 5 times mints an issue. On 2026-07-17 one source drove three clusters to 18-103 identical hits in a few hours ('synthora mesh', 'em spectrum intelligence', '962 m2m services'), each auto-opening a junk issue (now closed).
Fix
New
clusterQualifiespredicate in src/wish.js requires, on top of the count threshold, a sign of independent demand:QUALIFY_MIN_SPAN_MS).A genuine gap is hit by different agents across surfaces or recurs over days; a scripted burst is one source in one sitting and clears neither bar. Both observed synthora clusters (single-source, few-hour span) are now unqualified.
getWishesAggregateexposes a per-clusterqualifiedflag; the wish-issues workflow selects on.qualified == trueinstead of.count >= threshold. A cached pre-deploy response lacks the field, so the gate fails closed (opens nothing) during rollout.Honest limit, stated in-code (same framing as the router's per-seller Sybil cap): a patient spammer can still drip over 24h or add a decoy hit on a second surface. This raises the cost from 5 curls to sustained-or-multi-surface; it is not full Sybil resistance. Every wish is still recorded and visible on /api/wishes regardless; only auto-issue creation is gated.
Tests
scripts/test-wish.js:
clusterQualifiesunit cases (below-threshold, single-source burst, both synthora shapes, two-source, span boundary) plus end-to-end through recordWish + getWishesAggregate. Aggregate-shape exact-keys test and the demand-radar consumer test updated for the additive field. Full suite 35/35 wish + 36/36 demand-radar green; workflow YAML validated and the jq gate simulated (qualified selected, both bursts skipped, missing-field fail-closed).🤖 Generated with Claude Code