chore(licensing): the Signal licence endpoint no longer wears the retired STREETS brand - #497
Merged
Merged
Conversation
…ired STREETS brand /api/streets/activate was the Signal licence endpoint wearing the name of a product retired in June 2026 — every reader (and the Stripe dashboard row pointing at it) kept mistaking the production payment path for curriculum leftovers. Canonical route is now /api/license/activate, beside the existing /api/license/refresh. - functions/api/streets/activate.js stays as a 3-line re-export shim: the recovery URL is baked into pre-rename desktop binaries, and the Stripe event destination still points at the old URL until its dashboard edit. Both routes verified present in the compiled Worker. - setup-signal.mjs now checks all SIX handled events (charge.refunded, charge.dispute.created, charge.dispute.closed were missing) and accepts either path when locating the destination. - streets.4da.ai dropped from CORS: NXDOMAIN (verified against 1.1.1.1), nothing has served from it since STREETS retired. - 'No STREETS license found' 404 now matches the endpoint's other 404s. - test-e2e-stripe.mjs invalid-tier test hit /api/streets/checkout, a route that has never existed here (checkout is /api/signal/checkout) — it was asserting against the static 404 page, not the handler. - Stripe customer metadata is untouched: the streets_* -> signal_* namespace migration already shipped with a legacy read-fallback in lib/entitlement.js. - lib/recovery-email.js's historical comment left as-is (file claimed by a peer lane; the comment describes the 2026-08-14 era when the old path was canonical, so it remains accurate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U2x3UAWbQvqU2DiQL4pSK9
runyourempire
added a commit
that referenced
this pull request
Aug 21, 2026
…that lied (#500) ## Finishing the two hand-offs from #499 Both files were **claimed in flight by peer lanes** when the surrounding work landed, so they were documented and deliberately left alone rather than handing an active peer a mid-flight merge conflict. Both lanes have since released them. ### Badge overflow `Subscription renewed` is **20 characters** against a ~16 budget, and the email header has no media query to save it — Gmail strips `<style>` in several contexts, so the three header cells must fit unaided on a 320px phone. It overflowed, wrapped the row and squeezed the wordmark. Now **`Renewal`**. The test pinning the old string failed, which is exactly what it was for. Rather than just swap the literal, the assertion now checks the **budget**: it extracts whatever badge was rendered and fails if it exceeds `BADGE_MAX_CHARS`. The next person who writes a more descriptive badge fails *here* instead of in a customer's inbox. **Revert-checked** — restoring `Subscription renewed` makes it fail, naming both the offending string and its length. ### A comment that lied `license/activate.js` said *"there is no event-id dedup store"* while dedup has existed since **#494** — twenty lines from the `LICENSE_KV` call that implements it. **#497** moved the file and carried the wrong comment along with it. The fix is deliberately **not deletion**. The guard that comment sits above is still load-bearing, and saying so is the whole point: - **dedup** suppresses a *redelivery of the same event id* - **this guard** catches a genuinely *new* `invoice.paid` arriving after a refund or dispute Two different defences against two different failures. The comment now says which is which, instead of denying that the other one exists. ## Verification **212 script tests pass.** The badge guard is revert-checked in both directions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Fq96xWyPQjx2bCCzWtsnC9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
/api/streets/activateis the production Signal licence endpoint wearing the name of a product retired in June 2026. Every reader — human and agent — kept mistaking the payment path for curriculum leftovers; the operator was one dashboard click from deleting the live Stripe event destination because its URL reads as STREETS junk. The label is the hazard. This PR removes it.What
/api/license/activate(beside the existing/api/license/refresh).functions/api/streets/activate.jsbecomes a 3-line re-export shim: pre-rename desktop builds have the recovery URL baked into the binary, and the Stripe event destination points at the old URL until its one-time dashboard edit. Both routes verified present in the compiled Worker (wrangler pages functions build).settings_commands_license.rs) repointed to the new path.setup-signal.mjsnow checks all SIX handled events —charge.refunded,charge.dispute.created,charge.dispute.closedwere missing from its checklist while the deployed handler (fix(licensing): close the four live holes in the payment/recovery path #494/fix(licensing): wave 2 — session-lookup key leak, expiry enforcement, key-in-logs, +6 #495) handles them. It also finds the destination on either path.streets.4da.aidropped from CORS — NXDOMAIN (verified against 1.1.1.1); nothing has served from it since STREETS retired./api/streets/checkout, a route that has never existed on Cloudflare (checkout is/api/signal/checkout) — it was asserting against the static 404 page, not the handler's 400 branch.'No STREETS license found'404 now matches the endpoint's other 404 bodies.What is deliberately NOT touched
streets_*→signal_*namespace migration already shipped with a legacy read-fallback inlib/entitlement.js; no customer record is affected by this PR.lib/recovery-email.jshistorical comment — file is claimed by a peer lane (fix-open-code-17-21); the comment describes the 2026-08-14 era when the old path was canonical, so it stays accurate as written.streets_engine/streets_localization/docs/streets— retired-curriculum machinery unrelated to payments; renaming the DB column is a schema migration and a separate decision.Operator follow-up (after this deploys to 4da.ai)
One edit on the existing Stripe event destination (Workbench → Webhooks → ⋯ → Update details): set URL to
https://4da.ai/api/license/activateand select all six events (checkout.session.completed,invoice.paid,customer.subscription.deleted,charge.refunded,charge.dispute.created,charge.dispute.closed). Same signing secret — no env change, no gap. This simultaneously closes the open P0 (destination only subscribes to 3 of 6 events). If a WAF rate-limit rule was ever created for/api/streets/activate, add the new path to it.Verification
wrangler pages functions buildcompiles; bothapi/license/activateandapi/streets/activatepresent in the bundlecheck-file-sizes.cjsclean (warnings only, none from this diff)🤖 Generated with Claude Code
https://claude.ai/code/session_01U2x3UAWbQvqU2DiQL4pSK9