mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-10 18:59:56 +00:00
Career v3: Gold tier — verified improv upgrades an earned badge (#960)
* feat(career): Gold tier — a family-style goldImprov artifact upgrades an earned badge The drill-state relay's goldImprov map (virtuoso gold_improv mints, gained-only merged like drill nodes) turns an earned badge gold when the passport's genre — or its genre family — has a verified improv artifact. Gold never substitutes for the badge bar: gold-without-bronze stays in_progress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(career): Gold tier frontend — relay, ceremony, slam, gold ink everywhere The drill-state relay now carries virtuoso's goldImprov map; a badge that comes back gold gets its own ceremony + notification (tier-suffixed seen ids — the bronze moment stays seen under its legacy id, a gold slam marks both), a gold stamp slam in the book, gold ink on the shelf-cover mini stamp, and the real gold foil chip. The bronze page's dashed 'Gold rung coming' preview becomes a live invitation to jam the style in Virtuoso. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(career): gold review fixes — family-space style matching, intake guards, rail counter The review's showstopper: virtuoso mints goldImprov under raw STYLE_PALETTES ids ('punk', 'djent', 'disco'), which are mostly NOT family keys — the tier check now matches in family space (artifact style and passport genre bucket through the same _genre_family keyword match), so a 'punk' gold reaches a 'punk rock' passport. Also: non-dict goldImprov 400s loudly instead of silently dropping; evidence-free artifacts (no verifier) never mint; goldImprov gets the same pre-merge size bound byNode has (junk under the cap could otherwise persist forever and wedge every later relay at the post-merge check); the instrument-rail badge counter counts gold (earning gold no longer made a badge vanish from the rail); first-artifact-wins is now asserted against the persisted snapshot instead of vacuously. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0d35228d56
commit
be473dc7af
@@ -371,3 +371,77 @@ def test_gig_propose_backfill_offset_survives_stakes(client, meta_db):
|
||||
files = [s["filename"] for s in res.json()["songs"]]
|
||||
assert len(files) == 5 and len(set(files)) == 5
|
||||
assert "near.feedpak" in files
|
||||
|
||||
|
||||
# ── Gold rung ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_gold_upgrades_bronze_via_family_style_artifact(client, meta_db):
|
||||
# Bronze earned on 'death metal' (family: metal); a metal gold artifact
|
||||
# from the jam verifier upgrades it — bronze-only stays 'earned' elsewhere.
|
||||
for i in range(5):
|
||||
meta_db.add(f"dm{i}.feedpak", 0, 0.9, genre="Death Metal", arrangements=LEAD)
|
||||
career_routes._state["passports_content"]["genres"]["metal"] = {} # no drill gate for this test
|
||||
_open(client, "guitar", "Death Metal")
|
||||
client.post("/api/plugins/career/drill-state", json={"byNode": {}})
|
||||
assert _passport(client, "guitar", "death metal")["badge"] == "earned"
|
||||
client.post("/api/plugins/career/drill-state", json={
|
||||
"byNode": {}, "goldImprov": {"metal": {"at": 1, "verifier": "comb", "inKeyPct": 0.9}}})
|
||||
assert _passport(client, "guitar", "death metal")["badge"] == "gold"
|
||||
|
||||
|
||||
def test_gold_without_bronze_stays_in_progress(client, meta_db):
|
||||
meta_db.add("one.feedpak", 0, 0.9, genre="Soul", arrangements=LEAD)
|
||||
_open(client, "guitar", "Soul")
|
||||
client.post("/api/plugins/career/drill-state", json={
|
||||
"byNode": {}, "goldImprov": {"soul": {"at": 1, "verifier": "comb"}}})
|
||||
assert _passport(client, "guitar", "soul")["badge"] == "in_progress"
|
||||
|
||||
|
||||
def test_gold_merge_is_gained_only(client, meta_db):
|
||||
for i in range(5):
|
||||
meta_db.add(f"s{i}.feedpak", 0, 0.9, genre="Soul", arrangements=LEAD)
|
||||
_open(client, "guitar", "Soul")
|
||||
client.post("/api/plugins/career/drill-state", json={
|
||||
"byNode": {}, "goldImprov": {"soul": {"at": 1, "verifier": "comb"}}})
|
||||
assert _passport(client, "guitar", "soul")["badge"] == "gold"
|
||||
# A stale relay without the artifact never un-mints.
|
||||
client.post("/api/plugins/career/drill-state", json={"byNode": {}})
|
||||
assert _passport(client, "guitar", "soul")["badge"] == "gold"
|
||||
# And a different artifact for the same style never overwrites the first —
|
||||
# asserted against the PERSISTED snapshot (the view doesn't expose
|
||||
# artifact contents), so a last-write-wins regression can't stay green.
|
||||
client.post("/api/plugins/career/drill-state", json={
|
||||
"byNode": {}, "goldImprov": {"soul": {"at": 999, "verifier": "yin"}}})
|
||||
_, _, gold = career_routes._drill_by_node()
|
||||
assert gold["soul"] == {"at": 1, "verifier": "comb"}
|
||||
|
||||
|
||||
def test_gold_matches_raw_style_id_through_family(client, meta_db):
|
||||
# Virtuoso mints under raw STYLE_PALETTES ids ('punk', not 'rock'): a
|
||||
# 'punk rock' passport (family rock) must go gold from a 'punk' artifact.
|
||||
for i in range(5):
|
||||
meta_db.add(f"pk{i}.feedpak", 0, 0.9, genre="Punk Rock", arrangements=LEAD)
|
||||
career_routes._state["passports_content"]["genres"]["rock"] = {} # no drill gate
|
||||
_open(client, "guitar", "Punk Rock")
|
||||
client.post("/api/plugins/career/drill-state", json={
|
||||
"byNode": {}, "goldImprov": {"punk": {"at": 1, "verifier": "comb"}}})
|
||||
assert _passport(client, "guitar", "punk rock")["badge"] == "gold"
|
||||
|
||||
|
||||
def test_gold_intake_rejects_junk(client, meta_db):
|
||||
# A non-dict goldImprov is a relay bug: loud 400, never a silent drop.
|
||||
res = client.post("/api/plugins/career/drill-state",
|
||||
json={"byNode": {}, "goldImprov": ["metal"]})
|
||||
assert res.status_code == 400
|
||||
# Evidence-free artifacts (no verifier) never mint.
|
||||
for i in range(5):
|
||||
meta_db.add(f"j{i}.feedpak", 0, 0.9, genre="Soul", arrangements=LEAD)
|
||||
_open(client, "guitar", "Soul")
|
||||
client.post("/api/plugins/career/drill-state", json={
|
||||
"byNode": {}, "goldImprov": {"soul": {}}})
|
||||
assert _passport(client, "guitar", "soul")["badge"] == "earned"
|
||||
# An oversized goldImprov is bounded BEFORE the merge, like byNode.
|
||||
blob = {f"s{i}": {"verifier": "comb", "pad": "x" * 4096} for i in range(200)}
|
||||
res = client.post("/api/plugins/career/drill-state",
|
||||
json={"byNode": {}, "goldImprov": blob})
|
||||
assert res.status_code == 413
|
||||
|
||||
Reference in New Issue
Block a user