Skip to content

chore(licensing): the Signal licence endpoint no longer wears the retired STREETS brand - #497

Merged
runyourempire merged 1 commit into
mainfrom
worktree-chore+rename-streets-activate
Aug 20, 2026
Merged

chore(licensing): the Signal licence endpoint no longer wears the retired STREETS brand#497
runyourempire merged 1 commit into
mainfrom
worktree-chore+rename-streets-activate

Conversation

@runyourempire

Copy link
Copy Markdown
Collaborator

Why

/api/streets/activate is 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

  • Canonical route is now /api/license/activate (beside the existing /api/license/refresh). functions/api/streets/activate.js becomes 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).
  • Desktop app recovery URL (settings_commands_license.rs) repointed to the new path.
  • setup-signal.mjs now checks all SIX handled eventscharge.refunded, charge.dispute.created, charge.dispute.closed were 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.ai dropped from CORS — NXDOMAIN (verified against 1.1.1.1); nothing has served from it since STREETS retired.
  • Latent e2e bug fixed in passing: the invalid-tier test POSTed to /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.
  • Docs repointed: NETWORK.md (with a legacy-path note for pre-rename builds), DECISIONS.md AD-028 code pointer, validate.yml comment, generate-license.mjs, verify-ed25519-equivalence.mjs.

What is deliberately NOT touched

  • Stripe customer metadata — the streets_*signal_* namespace migration already shipped with a legacy read-fallback in lib/entitlement.js; no customer record is affected by this PR.
  • lib/recovery-email.js historical 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.
  • App-internal 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/activate and 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 build compiles; both api/license/activate and api/streets/activate present in the bundle
  • 64/64 site lib tests, 1242/1242 frontend tests, cargo fmt + clippy clean (pre-push gate)
  • check-file-sizes.cjs clean (warnings only, none from this diff)

🤖 Generated with Claude Code

https://claude.ai/code/session_01U2x3UAWbQvqU2DiQL4pSK9

…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
runyourempire merged commit af0f1da into main Aug 20, 2026
15 checks passed
@runyourempire
runyourempire deleted the worktree-chore+rename-streets-activate branch August 20, 2026 18:25
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant