lead claims: outreach v1, decided by Mike

Who picked a lead up and when, so nothing sits waiting unnoticed. No
outcomes, no end states, nothing on join_leads changes: the lead stays the
record of what the family said and the claim is only who has it. One row
per claim in lead_claims; the current claim is the latest unreleased.
Taking over is release then claim, never a silent overwrite: 409 names
the holder. The holder or an owner may release. Every claim and release
lands in the action log; the summary carries an unclaimed count.

tests/smoke_admin.py 112 -> 122.
This commit is contained in:
2026-09-04 18:34:01 -04:00
parent aaa77be14d
commit 59559ad909
3 changed files with 153 additions and 0 deletions
+22
View File
@@ -301,6 +301,28 @@ check("kind prefix filter and limit", all(e["kind"].startswith("nearby.") for e
check("history is admin and above, and not scopable on a key", "history:read" in I.CAPS["admin"]
and "history:read" not in I.CAPS["leader"] and "history:read" in I.KEY_UNSCOPABLE)
print("\nlead claims")
lid = S.record_lead({"parent_name": "Claim Test", "email": "claim@example.test", "interested_in": "Pack 73"}) if hasattr(S, "record_lead") else None
if lid is None:
con = S.connect(); lid = "lead-claim-test"
con.execute("INSERT INTO join_leads (id, submitted_at, recorded_at, parent_name, email, payload) VALUES (?,?,?,?,?,?)",
(lid, I._now(), I._now(), "Claim Test", "claim@example.test", "{}")); con.commit(); con.close()
check("unclaimed count sees it", S.unclaimed_count() >= 1 and S.summary()["unclaimed"] >= 1)
c = S.claim_lead(lid, "p-a", "a@example.test")
check("claim recorded and listed on the lead", c["email"] == "a@example.test" and S.get_lead(lid)["claim"]["email"] == "a@example.test"
and [r for r in S.list_leads(limit=500) if r["id"] == lid][0]["claim"]["email"] == "a@example.test")
raises("claiming again yourself", 409, S.ClaimRejected, S.claim_lead, lid, "p-a", "a@example.test")
raises("someone else cannot take it over silently", 409, S.ClaimRejected, S.claim_lead, lid, "p-b", "b@example.test")
raises("someone else cannot release it", 403, S.ClaimRejected, S.release_lead, lid, "p-b", "b@example.test")
check("an owner can release it", S.release_lead(lid, "p-b", "b@example.test", can_release_any=True)["email"] == "a@example.test"
and S.get_lead(lid)["claim"] is None)
raises("release when nobody has it", 409, S.ClaimRejected, S.release_lead, lid, "p-a", "a@example.test")
S.claim_lead(lid, "p-b", "b@example.test"); S.release_lead(lid, "p-b", "b@example.test")
check("history keeps every claim", len(S.get_lead(lid)["claim_history"]) == 2 and all(h["released_at"] for h in S.get_lead(lid)["claim_history"]))
check("nothing on the lead itself changed", "status" not in S.get_lead(lid) and "claimed" not in S.get_lead(lid))
check("unknown lead is None", S.claim_lead("nope", "p", "e") is None and S.release_lead("nope", "p", "e") is None)
con = S.connect(); con.execute("DELETE FROM lead_claims WHERE lead_id=?", (lid,)); con.execute("DELETE FROM join_leads WHERE id=?", (lid,)); con.commit(); con.close()
print("\napi docs registry")
import admin_api as A
reg = A.describe_routes()