# SmartCut64 Lemon Squeezy licensing bridge

This Cloudflare Worker converts a verified Lemon Squeezy purchase into the
perpetual, machine-bound `.sc64` entitlement already verified by the desktop
application, worker and CLI. The SmartCut64 private entitlement-signing key is
held only as a Worker secret. It must never enter the desktop source, build,
package, CI logs or Lemon Squeezy product files.

## Purchase flow

1. Lemon Squeezy creates a customer licence key at checkout.
2. SmartCut64 sends the key, purchase email and its one-way Installation ID to
   `POST /v1/activate`.
3. The bridge non-mutatingly validates the exact Lemon store/product/variant
   and customer email, then proves its private signing key matches the public
   key pinned to that Lemon product.
4. It prepares the RSA-signed perpetual `.sc64` entitlement in memory and
   durably records a privacy-safe `activation_pending` intent before consuming
   a Lemon activation. Pending recovery phases are stored with legacy-visible
   `status=revoked`, so the currently deployed v1 Worker also fails closed if it
   must become the rollback target after candidate traffic.
5. The bridge activates the installation, commits its exact Lemon instance
   UUID and active state, and returns the entitlement for that Installation ID
   (`expires_unix=0`). An ambiguous response stays pending; once that KV intent
   is visible, the same isolate and sequential retries fail closed instead of
   blindly consuming another seat.
6. SmartCut64 verifies and caches that entitlement locally. No periodic online
   check is required for clean export on that activated installation.

The bridge stores only a SHA-256 digest of the Lemon key, the already-hashed
SmartCut64 Installation ID, Lemon instance/order/licence identifiers and the
customer identity required for support. It does not store raw Windows
MachineGuid values or raw Lemon licence keys.

## Endpoints

- `GET /health` and `GET /ready` (configuration, signing-key and state-binding readiness)
- `POST /v1/activate`
- `POST /v1/deactivate`
- `POST /v1/webhooks/lemon`

Webhook requests are accepted only when Lemon Squeezy's `X-Signature` HMAC
matches the configured signing secret. Full refunds and disabled keys block
future activations, transfers and support-assisted resets. An already-issued
perpetual offline entitlement cannot be remotely revoked; the customer terms
must state the post-refund obligation accurately.

Activation and deactivation are protected by separate per-licence and per-actor
Cloudflare native rate-limit bindings. These slow guessing and reduce pressure
on Lemon Squeezy's 60-request-per-minute License API budget; they are not a
global cross-location quota, so upstream 429 responses must also be monitored. A
missing or unavailable rate-limit binding fails closed; it never silently
turns protection off.

Refund and disabled-key revocation uses both per-activation records and a
licence-level revocation marker. The activation index is paginated, so a
licence with more than 100 historical installations cannot leave older
activation records eligible for transfer after a refund.

## Configure and test

1. Install Node.js 20 or later and pnpm 11.19.0.
2. Run `pnpm install --frozen-lockfile` and `pnpm test` in this directory. The
   committed lockfile pins Wrangler and its complete deployment dependency tree.
3. Copy `wrangler.toml.example` to the ignored `wrangler.toml`.
4. Create separate Cloudflare KV namespaces for test and live state and enter
   their IDs in `wrangler.toml`. Write the fixed key
   `system:namespace-marker:v1` with value
   `smartcut64-license-state:test:24c50e674aed45f9830034c9451706e3:v1`
   in the current test namespace and
   `smartcut64-license-state:production:b48acd15f08540abbdb7026cbdbb0319:v1`
   in the current live namespace. Each value is pinned to both its Lemon
   product and physical namespace identity, so a swapped or unseeded binding
   fails closed.
5. Set the exact Lemon Squeezy test store, product and variant IDs.
6. Replace the two rate-limit namespace IDs with unique positive integers for
   each test/live deployment.
7. Set `SMARTCUT_LICENSE_PUBLIC_MODULUS_HEX` to the exact public modulus
   compiled into the corresponding SmartCut64 build. The Worker also pins the
   published test and production modulus fingerprints to their Lemon product
   IDs, so changing both the secret and variable cannot bless the wrong key.
8. Configure `SMARTCUT_PRIVATE_KEY_PEM` and `LEMON_WEBHOOK_SECRET` as Worker
   secrets; never place their values in a file or command line.
9. Run `pnpm dev`, exercise Lemon test purchases and deploy the test service
   with `pnpm deploy`.

## Fail-safe production deployment

Never use an immediate production `wrangler deploy` or `wrangler secret put`
for signing-key changes. Upload code, configuration or secrets as an
undeployed Worker version, then use this gate:

1. Run `pnpm test`, confirm the currently active version ID, and read back the
   product-specific `system:namespace-marker:v1` from the exact configured KV
   namespace. Pipe that read to `pnpm check:state-marker production`. If a new
   test namespace is being commissioned, create a new unique marker and pin it
   in source. Never commission or rebind production merely by copying its
   marker into an empty namespace: production replacement requires a frozen,
   verified migration of every activation, recovery, index and revocation
   record, plus a newly pinned physical-namespace marker and release review.
2. Upload the candidate with `wrangler versions upload`, or create a secret
   candidate with `wrangler versions secret put`. Before the latter, confirm
   the most recently uploaded version is also the current 100% active version;
   Wrangler 4.36 clones the latest uploaded version.
3. Inspect the uploaded candidate with `wrangler versions view
   <candidate-version> --json`. Pipe the JSON to `pnpm check:version-bindings
   <candidate-version> <production-kv-namespace-id>`, which proves the exact KV,
   version-metadata and secret bindings. Only then create a deployment containing
   `<old-version>@100` and `<candidate-version>@0`.
4. Run `pnpm check:readiness -- <service-url> <candidate-version>
   <worker-name>`. The request uses Cloudflare's version-override header and
   succeeds only if the candidate reports its own version ID and completes a
   private-key sign/public-key verify self-test and reads the exact
   product-pinned KV namespace marker.
5. Promote the candidate to 100%. Pipe `wrangler deployments status --json`
   into `pnpm check:deployment -- <candidate-version>` and require it to report
   that candidate as the sole 100% version. Then run `pnpm check:readiness --
   <service-url> <candidate-version>` without a Worker name to prove default
   routing serves it. Restore the old version immediately if any check fails.

Do not change the guarded pending-state encoding without first proving rollback
compatibility. `activation_pending` and `rollback_pending` are carried in
`recovery_status` while the legacy `status` remains `revoked`; deployed v1
therefore blocks rather than entering its fresh-activation branch.

`pnpm smoke:production` is a convenient ongoing readiness check, but it does
not replace the candidate-version assertion during a deployment.

`/ready` and `/health` never call Lemon or write KV. They perform one read of a
non-customer namespace marker. Activation performs the same cached key-pair
proof and marker check before it reads customer state or calls Lemon. A
malformed, valid-but-wrong, or wrong-product signing key, and an empty or
test/live-swapped state binding, therefore fail before they can consume a
customer's activation seat. If an existing Lemon instance is reported invalid,
the Worker verifies the key-only purchase and confirms exact-instance cleanup
before it can request a replacement activation. Identical requests in one
Worker isolate are serialized; the durable pre-activation intent blocks
sequential ambiguous retries. Workers KV is still eventually consistent across
locations, so strongly consistent coordination is required before claiming a
global at-most-once guarantee for simultaneous cross-region requests.

## Recover an ambiguous activation

An `activation_pending` record means Lemon may have consumed a seat but the
Worker never received and committed a trustworthy instance UUID. Never delete
or simply clear this record.

1. Obtain the customer's licence key and Installation ID through the private
   support channel. Do not place the raw key in a command line, ticket note or
   log.
2. In the Lemon Squeezy dashboard, open the exact licence and inspect every
   instance whose name exactly equals `SmartCut64 <installation_id>`.
3. If there are matches, deactivate every matching UUID and verify that none
   remains active. If there are no matches, capture authoritative evidence of
   that fact. Multiple matches are an incident and all must be reconciled.
4. From this directory, run `scripts/Recover-ActivationPending.ps1` with the
   Installation ID and either `-ConfirmedNoLemonInstance` or every verified
   `-DeactivatedInstanceId`. The script prompts invisibly for the licence key,
   proves the production namespace marker, accepts only the guarded (or legacy
   direct) `activation_pending` phase, preserves the record, records the
   recovery evidence, and verifies readback.
5. Retain a private audit note containing the time, operator, Lemon evidence
   and recovered UUIDs, then ask the customer to retry. Do not include the raw
   licence key or customer identity in a repository file.

The recovery script is intentionally production-only and uses `ShouldProcess`
with high confirmation impact. Run `-WhatIf` first when rehearsing the command.

Test and live deployments must use different hostnames, KV namespaces,
entitlement key pairs, Lemon IDs and webhook secrets. The live private key
should be imported from an independently backed-up production key under formal
key-custody procedures.

## Required Lemon Squeezy settings

- Single-payment software product
- Generated licence keys enabled
- Activation limit: 2 for the initial Personal variant
- Webhook events: `order_refunded` and `license_key_updated`
- Callback: `https://<licensing-host>/v1/webhooks/lemon`

SmartCut64 is GPL-3.0-or-later. The activation service distinguishes the
seller's official signed binaries, clean-export entitlement, updates and
support; it must not be described or used as a contractual restriction on the
GPL rights to inspect, modify, build or redistribute the source.
