Sync repo to deployed state: SEO recovery, Publishing Desk, Play games, emoji picker

The deploy pipeline runs from the working tree, so a wave of shipped features
had never been committed. This snapshots git to what's actually running.

SEO impression recovery (live + verified):
- Duplicate /a/{id} now 301-redirect to their canonical twin instead of 404
  (a hard 404 silently dropped already-indexed URLs and tanked impressions).
- Dedup representative selection reworked: accepted/serveable -> established
  rep (URL stability) -> quality score, so an accepted page never retires to a
  rejected rep and an indexed canonical doesn't churn when a newer twin arrives.
- HEAD /a/{id} returns the same status as GET (api_route GET+HEAD) instead of
  falling through to the static mount and 404ing.
- `dedup --force-recluster`: cycle-locked, model-free re-cluster to re-apply the
  policy to the existing corpus (shared cycle_lock context manager).
- CLI honors GOODNEWS_DB for its default --db (was silently ignored).

Publishing Desk (admin tool to post highlights to X via Web Intents):
- publishing.py queue/rank/handle-resolution; admin UI; full searchable emoji
  picker (bundled data, no CDN) for the blurb editor.

Play games + site:
- Bloom (word-wheel), Memory Match, daily ritual set, Zen Den (dev-gated).
- English-only language gate; source prospecting; paywall + dedup hardening.

Tests: full suite green (349). Ignores tightened (node_modules, data/*.db).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-18 11:32:27 -04:00
parent 2dbe73430c
commit 89c0fbe1f6
66 changed files with 6138 additions and 109 deletions
+317
View File
@@ -0,0 +1,317 @@
"""Bloom — the daily word wheel (Center Circle / Wild Bloom).
DESIGN and ACCEPTANCE are decoupled:
• DESIGN (wheel selection, tiers, pangram, the Full-Bloom target) uses the small
COMMON list only — deterministic, stored in daily_puzzles, and unaffected by
curation. Tiers are scored on COMMON so "Flourishing" is always reachable with
everyday vocabulary, and "Full Bloom" = finding the whole *designed* puzzle
(the broad bonus words are extra credit beyond it, never required).
• ACCEPTANCE is BROAD and DYNAMIC — every valid dictionary word buildable from
the wheel, computed at RESPONSE TIME as: broad dict {allow} {block}, where
allow/block are runtime admin overrides (bloom_word_overrides). So a missed
word can be allowed (or a junk word blocked) with NO deploy or regeneration.
Accept words never sit in the network response: clients validate against salted
hashes and compute their own score/tier/pangram from the 7 letters.
"""
from __future__ import annotations
import hashlib
import json
import random
import sqlite3
from itertools import combinations
from pathlib import Path
_DATA = Path(__file__).parent / "data"
_W = json.loads((_DATA / "bloom_words.json").read_text())
ACCEPT: list[str] = _W["accept"] # broad: all valid dictionary words
_COMMON: set[str] = set(_W["common"]) # tight: design / tiers / pangrams only
_COMMON_LS: list[tuple[str, frozenset]] = [(w, frozenset(w)) for w in _COMMON]
_AVOID: set[str] = set(json.loads((_DATA / "bloom_avoid.json").read_text()))
# Broad accept words bucketed by distinct-letter set, so the accepted set for a
# 7-letter wheel is gathered by unioning its ≤127 letter-subsets (fast) — no scan
# of the whole ~68k list per request.
_BY_SET: dict[frozenset, list[str]] = {}
for _w in ACCEPT:
_BY_SET.setdefault(frozenset(_w), []).append(_w)
# Candidate wheels = letter-sets of 7-distinct-letter COMMON words (every wheel
# has ≥1 recognizable pangram). Sorted for deterministic order.
_PANGRAM_SETS: dict[frozenset, list[str]] = {}
for _w in _COMMON:
_s = frozenset(_w)
if len(_s) == 7:
_PANGRAM_SETS.setdefault(_s, []).append(_w)
_CANDIDATES: list[frozenset] = sorted(_PANGRAM_SETS, key=lambda s: "".join(sorted(s)))
MIN_COMMON_WORDS, MAX_COMMON_WORDS = 14, 45
PANGRAM_BONUS = 7
# 8 / 30 / 70 — Flourishing at 70% keeps Bloom from becoming a completionist
# grind. Do NOT raise Flourishing above 0.70 (Codex).
TIER_PCTS: tuple[tuple[str, float], ...] = (
("Sprouting", 0.0), ("Budding", 0.08), ("Blooming", 0.30), ("Flourishing", 0.70),
)
TOP_TIER_PCT = 0.70
def score_word(word: str) -> int:
"""4-letter word = 1 point; longer = its length. Pangram bonus added on top."""
return 1 if len(word) == 4 else len(word)
def score_words(payload: dict, words) -> int:
"""Score found words for a wheel (pangram = uses all 7 letters). Used for the
player's running score AND the Full-Bloom check (vs the design's max_score)."""
letters = frozenset(payload["center"]) | frozenset(payload["outer"])
total = 0
for w in words:
total += score_word(w)
if frozenset(w) == letters:
total += PANGRAM_BONUS
return total
# --- DESIGN: common-only, deterministic, stored --------------------------------
def tiers_for(common_max: int) -> list[dict]:
return [{"name": n, "score": int(p * common_max)} for n, p in TIER_PCTS]
def _design(letters: frozenset, center: str):
"""Center-mode design from the COMMON list only."""
commons = [w for (w, s) in _COMMON_LS if center in w and s <= letters]
pangrams = [w for w in commons if frozenset(w) == letters]
common_max = sum(score_word(w) for w in commons) + PANGRAM_BONUS * len(pangrams)
display = sorted((p for p in pangrams if p not in _AVOID), key=lambda p: (len(p), p))
return commons, display, common_max
def _design_wild(letters: frozenset):
"""Wild design (no required center) from the COMMON list only."""
commons = [w for (w, s) in _COMMON_LS if s <= letters]
pangrams = [w for w in commons if frozenset(w) == letters]
common_max = sum(score_word(w) for w in commons) + PANGRAM_BONUS * len(pangrams)
display = sorted((p for p in pangrams if p not in _AVOID), key=lambda p: (len(p), p))
vowels = [c for c in sorted(letters) if c in "aeiou"]
return commons, display, common_max, (vowels[0] if vowels else sorted(letters)[0])
def _payload(letters: frozenset, center: str, display, common_max: int) -> dict:
return {
"center": center,
"outer": sorted(letters - {center}),
"pangram": display[0],
"tiers": tiers_for(common_max),
# Full Bloom = finding the whole designed (common) puzzle; broad bonus
# words push score past this but are never required.
"max_score": common_max,
}
def _generate(seed_str: str, fmt: str) -> dict:
"""Deterministically pick a wheel design for a seed + format."""
rng = random.Random(int(hashlib.sha256(seed_str.encode()).hexdigest(), 16))
order = _CANDIDATES[:]
rng.shuffle(order)
for letters in order:
if fmt == "wild":
commons, display, cmax, center = _design_wild(letters)
if len(commons) >= MIN_COMMON_WORDS and display:
return _payload(letters, center, display, cmax)
else:
centers = sorted(letters)
rng.shuffle(centers)
for center in centers:
commons, display, cmax = _design(letters, center)
if MIN_COMMON_WORDS <= len(commons) <= MAX_COMMON_WORDS and display:
return _payload(letters, center, display, cmax)
raise RuntimeError("bloom: no valid wheel found") # impossible with the vendored dict
def build_puzzle(date: str) -> dict:
"""The day's shared Center Circle wheel design (deterministic by date)."""
return {"date": date, **_generate(f"bloom:{date}", "center")}
def build_free(seed: str, fmt: str = "center") -> dict:
"""A free-play wheel design (deterministic by seed) — Center Circle or Wild."""
fmt = "wild" if fmt == "wild" else "center"
return {"seed": seed, "format": fmt, **_generate(f"free:{fmt}:{seed}", fmt)}
# --- ACCEPTANCE: broad + runtime overrides, computed at response time ----------
def overrides(conn: sqlite3.Connection) -> tuple[set, set]:
allow, block = set(), set()
for r in conn.execute("SELECT word, action FROM bloom_word_overrides"):
(allow if r["action"] == "allow" else block).add(r["word"])
return allow, block
def _broad_words_for(letters: frozenset) -> list[str]:
"""Every broad-dictionary word buildable from `letters` (distinct-set ⊆ letters)."""
ls = sorted(letters)
out = []
for r in range(1, len(ls) + 1):
for combo in combinations(ls, r):
out.extend(_BY_SET.get(frozenset(combo), ()))
return out
def accepted_words(conn: sqlite3.Connection, center: str, outer, require_center: bool) -> list[str]:
"""The wheel's accepted set RIGHT NOW: broad words buildable from the letters
(optionally requiring the center), plus allow-overrides, minus block-overrides."""
letters = frozenset(outer) | {center}
allow, block = overrides(conn)
seen, out = set(), []
for w in _broad_words_for(letters):
if w in seen or w in block:
continue
if require_center and center not in w:
continue
seen.add(w)
out.append(w)
for w in allow: # allow words that may not be in the broad dict
if w in seen or w in block or len(w) < 4 or "s" in w:
continue
if not (frozenset(w) <= letters) or (require_center and center not in w):
continue
seen.add(w)
out.append(w)
return sorted(out)
# --- daily_puzzles storage -----------------------------------------------------
def generate_bloom_puzzle(conn: sqlite3.Connection, date: str) -> dict:
"""Ensure the day's Bloom DESIGN exists in daily_puzzles. Idempotent, pure code."""
existing = conn.execute(
"SELECT payload_json FROM daily_puzzles WHERE puzzle_date=? AND game='bloom' AND variant=''", (date,)
).fetchone()
if existing:
return json.loads(existing["payload_json"])
payload = build_puzzle(date)
conn.execute(
"INSERT OR IGNORE INTO daily_puzzles (puzzle_date, game, variant, payload_json) VALUES (?, 'bloom', '', ?)",
(date, json.dumps(payload)),
)
conn.commit()
row = conn.execute(
"SELECT payload_json FROM daily_puzzles WHERE puzzle_date=? AND game='bloom' AND variant=''", (date,)
).fetchone()
return json.loads(row["payload_json"])
def stored_payload(conn: sqlite3.Connection, date: str) -> dict | None:
"""The day's design IF it already exists — never generates (used by the state
sanitizer, which must not trigger generation)."""
row = conn.execute(
"SELECT payload_json FROM daily_puzzles WHERE puzzle_date=? AND game='bloom' AND variant=''", (date,)
).fetchone()
return json.loads(row["payload_json"]) if row else None
def word_hash(salt: str, word: str) -> str:
return hashlib.sha256(f"{salt}:{word}".encode()).hexdigest()
def _response(salt: str, p: dict, words: list[str], extra: dict) -> dict:
return {
"game": "bloom",
"center": p["center"],
"outer": p["outer"],
"accepted": [word_hash(salt, w) for w in words], # NO plaintext words leak
"max_score": p["max_score"], # Full Bloom = designed puzzle
"tiers": p["tiers"],
**extra,
}
def bloom_response(conn: sqlite3.Connection, date: str) -> dict:
"""Daily Center Circle — accepted set computed live (broad + overrides)."""
p = generate_bloom_puzzle(conn, date)
words = accepted_words(conn, p["center"], p["outer"], require_center=True)
return _response(date, p, words, {"date": date})
def bloom_free_response(conn: sqlite3.Connection, seed: str, fmt: str) -> dict:
"""Free-play wheel keyed by `seed` (resumable). Accepted set computed live."""
p = build_free(seed, fmt)
words = accepted_words(conn, p["center"], p["outer"], require_center=p["format"] != "wild")
return _response(seed, p, words, {"mode": "free", "format": p["format"], "seed": p["seed"]})
# --- runtime curation: overrides + player reports ------------------------------
def set_override(conn: sqlite3.Connection, word: str, action: str, reason: str | None = None,
by: str | None = None) -> bool:
word = (word or "").strip().lower()
if not (word.isalpha() and action in ("allow", "block")):
return False
# An ALLOW that violates Bloom's hard rules (≥4 letters, no 'S') could never
# count — reject it rather than store an inert override. BLOCK stays permissive.
if action == "allow" and (len(word) < 4 or "s" in word):
return False
conn.execute(
"INSERT INTO bloom_word_overrides (word, action, reason, created_by) VALUES (?,?,?,?) "
"ON CONFLICT(word) DO UPDATE SET action=excluded.action, reason=excluded.reason, "
"created_by=excluded.created_by, created_at=CURRENT_TIMESTAMP",
(word, action, reason, by),
)
conn.commit()
return True
def clear_override(conn: sqlite3.Connection, word: str) -> None:
conn.execute("DELETE FROM bloom_word_overrides WHERE word=?", ((word or "").strip().lower(),))
conn.commit()
def list_overrides(conn: sqlite3.Connection) -> list[dict]:
return [dict(r) for r in conn.execute(
"SELECT word, action, reason, created_by, created_at FROM bloom_word_overrides ORDER BY created_at DESC")]
def add_report(conn: sqlite3.Connection, word: str, puzzle_date, mode, fmt, letters, reason) -> bool:
word = (word or "").strip().lower()
if not (word.isalpha() and 4 <= len(word) <= 24):
return False
# Don't pile up duplicate pending reports for the same word.
dup = conn.execute(
"SELECT 1 FROM bloom_word_reports WHERE word=? AND status='pending'", (word,)).fetchone()
if dup:
return True
conn.execute(
"INSERT INTO bloom_word_reports (word, puzzle_date, mode, format, letters, reason) "
"VALUES (?,?,?,?,?,?)",
(word, str(puzzle_date or "")[:16], str(mode or "")[:8], str(fmt or "")[:8],
str(letters or "")[:16], str(reason or "")[:60]),
)
conn.commit()
return True
def list_reports(conn: sqlite3.Connection, status: str = "pending", limit: int = 100) -> list[dict]:
return [dict(r) for r in conn.execute(
"SELECT id, word, puzzle_date, mode, format, letters, reason, status, created_at "
"FROM bloom_word_reports WHERE status=? ORDER BY created_at DESC LIMIT ?", (status, limit))]
def resolve_report(conn: sqlite3.Connection, report_id: int, action: str, by: str | None = None) -> bool:
"""action: 'approve' (→ allow override) | 'block' (→ block override) | 'dismiss'."""
status = {"approve": "approved", "block": "blocked", "dismiss": "dismissed"}.get(action)
row = conn.execute("SELECT word FROM bloom_word_reports WHERE id=?", (report_id,)).fetchone()
if not row or not status:
return False
if action == "approve":
if not set_override(conn, row["word"], "allow", reason="report", by=by):
return False # can't allow (hard rule) — leave pending; dismiss instead
elif action == "block":
set_override(conn, row["word"], "block", reason="report", by=by)
conn.execute("UPDATE bloom_word_reports SET status=? WHERE id=?", (status, report_id))
conn.commit()
return True