feat(birth): transactional birth, single-flight ceremony hub, signup abuse gates (S3)#301
Conversation
Review — merge after fixes 🔴 (adversarial pass)The headline claims hold: single-flight birth is genuinely atomic in-process (synchronous MUST-FIX 1 — birth crash-window still wedges a half-born soul (Med-High). MUST-FIX 2 — birth LLM inference bypasses the billing preflight entirely (policy call for you). Non-blocking: Turnstile default-off, disposable-list is a tiny exact-match set, |
…abuse gates (S3) Hardens the path from signup to a living soul (PLAN_WEB_SIGNUP S3): - birth.ts: the LLM dream now runs BEFORE any durable write, with one retry; a failed dream leaves NO half-born soul (previously writeSeed ran first, so failure left isBorn()=true with no name and 'lisa birth' refused to re-run). Post-seed persist failures roll the seed back. Injectable dreamFn test seam. - birth-hub.ts: single-flight per-uid birth shared by the background lazy path and the visible POST /api/birth ceremony — one dream, any number of watchers; late SSE watchers replay the step transcript then go live. Cross-instance dedup via a Firestore birth lease when B9 is on. - Signup gates: Cloudflare Turnstile (default-OFF, fails closed), disposable-email blocklist (+LISA_EMAIL_BLOCKLIST), 5/hour/IP registration cap — every signup ignites an LLM call + a free window. - Soul locks: SOUL_LOCK_PATH / git lock paths were import-time constants frozen to the GLOBAL home, so cloud tenants serialized on one shared lock — now functions per the paths.ts doctrine. - Soul git history defaults OFF on the cloud edition (gcsfuse: many-small- file git ops are slow/fragile); LISA_SOUL_GIT=1/0 force-overrides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review + rebase onto
|
1b40dae to
d1718e9
Compare
Part of PLAN_WEB_SIGNUP_v1.0 — milestone S3. Stacked on #299 (S2) → #297 (S1).
What
Makes "注册完成自动进行 birth 流程" production-grade — the ceremony is visible, atomic, deduplicated, and bot-guarded.
Birth transactionality
The LLM dream now runs before any durable write, with one retry. Previously
writeSeed+initSoulReporan first — a failed/unparseable LLM call leftisBorn()=truewith no name or identity, andlisa birthrefused to re-run ("already born"): a half-born soul with no way out short of hand-deletingseed.json. Now a failed dream leaves nothing behind; a persist failure after the seed rolls it back. Covered by tests via an injectabledreamFnseam.Single-flight ceremony (
birth-hub.ts)The background lazy path (first authed request) and the visible SSE ceremony (
POST /api/birth) used to each run their own LLM call and race onwriteSeed. They now share one run per uid; a watcher that attaches late replays the step transcript before streaming live — the typewriter ceremony stays complete even when the lazy path started dreaming first. With Firestore (B9) on, abirth-<uid>lease dedupes across instances.Signup abuse gates
Every registration ignites an LLM call and mints a free window (–5/12h) — a direct cost target:
LISA_TURNSTILE_SITE_KEY/SECRET; fails closed when Cloudflare is unreachable)LISA_EMAIL_BLOCKLISTdeploy-time extension)Tenancy debt from the plan review
SOUL_LOCK_PATHand the soul git lock were import-time constants frozen to the global home — every cloud tenant serialized on one shared lock outside its own repo. Both are functions now (paths.ts doctrine).LISA_SOUL_GITforce-overrides both ways. Mac edition unchanged.Tests
npm test: 1319 pass / 0 fail. New: birth transactionality (3), birth hub (5), turnstile + blocklist (6).Operator steps (not in this PR)
Create a Turnstile widget for cloud.meetlisa.ai in the Cloudflare dash, then deploy with the two
LISA_TURNSTILE_*vars.🤖 Generated with Claude Code