ADR 0004 — Six-signal scoring (Sprint C.2)
Status: Accepted · 2026-04-17 Deciders: Raj Supersedes: — Extends: ADR 0003 (scoring architecture)
Context
ADR 0003 locked the scoring skeleton around one live signal (meme) plus five
stubs. Sprint C.2 expands the stubs into the remaining five signals — name,
social, image, creator, risk.
Two of them (name, social) need no external APIs and can ship today. Three
(image, creator, risk) need APIs still pending operational deps:
- image — Claude Vision API (Dep.2.4, ANTHROPIC_API_KEY already wired)
- creator — Bitquery streaming for on-chain wallet history (Dep.2.7)
- risk — GoPlus Security contract scan (Dep.2.8)
Decision
1. Heuristic-first for name and social
Both ship as pure deterministic functions with no external calls:
- name — length/casing rules, red-flag term blocklist, clone-suffix penalty (e.g. PEPE2/DOGE3).
- social — X handle presence + format + length, creator wallet presence, zero-address guard.
These are real signals (stub: false), not placeholders. They contribute to
the aggregate and satisfy the hasStubs === false gate for on-chain attestation
on their own merit.
Why heuristic not LLM: pure functions are cheaper, deterministic, and testable. Name/social quality is a well-defined rule-based problem; LLM latency would dominate the synchronous 30s budget for marginal gain.
2. Informative stubs for image, creator, risk
All three ship as stubs that inspect the submission shape, set
stub: true, and return a reason that names the exact future work:
"pending C.2.1", "pending C.2.2", "pending C.2.3".
Why stubs not hidden: a stub: true signal surfaces in the UI "Why this
score?" modal (Sprint C.4) and is caught by the C.3 attestation publisher's
hasStubs === true refusal. This keeps the incomplete scoring path
visibly incomplete — creators see why their score can't be attested; ops
sees which sub-sprint unblocks production.
Rejected alternative: returning score 50 with stub: false and a generic
reason would leak into on-chain attestations as false positives. The whole
point of the ADR 0003 hasStubs rail is that stubs refuse to be hidden.
3. Parallel computation via Promise.all
All six signal computers run in parallel in scoreToken. The three heuristic
signals resolve in 1ms; meme waits on Anthropic (1-5s); the other three
stubs resolve instantly. Wall clock is dominated by the Anthropic call.
Why parallel: when C.2.1-C.2.3 land with real API calls, parallelism becomes load-bearing — six synchronous calls would blow the 30s budget; six parallel calls stay under 5s p99.
4. Green band is now reachable
With only meme live in C.1, the max aggregate was ~65 (amber) because five
stubs at score 50 pinned the ceiling. In C.2, a submission with strong
social presence (X handle + wallet linked) + strong meme originality can
legitimately cross 70 → green. This is important for creator-facing UX: the
scoring gate must be achievable without all six signals being real, otherwise
no submission gets green until Dep.2.7 and Dep.2.8 both land.
Caveat: hasStubs: true still holds, so green scores are not yet attestable
on-chain. Creators see the green badge in the UI; the C.3 publisher refuses
until all six signals are real.
Consequences
Positive:
- Two live signals today (name, social) without waiting on ops deps.
- Scoring aggregate ranges across all three bands — scorecard UX is real.
- Sub-sprints (C.2.1, C.2.2, C.2.3) are independent and unblockable in any order as external APIs come online.
- Parallel dispatch is already baked in for when real API signals land.
Negative:
- Three signals remain stubs until external APIs land. Three separate integration sprints ahead (C.2.1 vision, C.2.2 Bitquery, C.2.3 GoPlus).
- Heuristic signals are easy to game (pick a 3-5 char uppercase symbol → guaranteed +55 on name). We accept this for C.2 and revisit in C.8 after calibration data shows whether adversarial creators are exploiting it.
Open questions (for sub-sprints)
- C.2.1 vision: base64 vs URL passthrough for images; cost envelope (Claude Vision is ~4× text).
- C.2.2 creator: streaming Bitquery subscription vs scheduled pulls; caching window (24h feels right — Four.meme creators rarely deploy > once/day).
- C.2.3 risk: GoPlus free-tier rate limits; fallback to on-chain calls when rate-limited; what counts as a "critical" vs "warning" risk factor in the final score mapping.
These are explicitly sub-sprint decisions, out of scope here.